Вопрос
В настоящее время я страдаю пердежом мозга.Я делал это раньше, но не могу вспомнить точный синтаксис и не могу просмотреть написанный мной код, поскольку в то время работал в другой компании.У меня есть такая договоренность:
class P
{
// stuff
};
class PW : public P
{
// more stuff
};
class PR : public P
{
// more stuff
};
class C
{
public:
P GetP() const { return p; }
private:
P p;
};
// ...
P p = c.GetP( ); // valid
PW p = c.GetP( ); // invalid
PR p = c.GetP( ); // invalid
// ...
Теперь я хотел бы сделать P взаимозаменяемым с PW и PR (и, таким образом, PW и PR можно менять местами).Вероятно, мне бы сошло с рук приведение типов, но только в этом модуле такое изменение кода происходило довольно много раз.Я почти уверен, что это оператор, но хоть убей, не могу вспомнить, что именно.
Как сделать P взаимозаменяемым с PW и PR с минимальным количеством кода?
Обновлять: Чтобы дать немного больше разъяснений.P означает Project, а R и W означает Reader и Writer соответственно.Все, что есть у Reader, — это код для загрузки — никаких переменных, а у Writer есть код для простой записи.Он должен быть отдельным, поскольку разделы «Чтение» и «Запись» содержат различные классы и диалоги менеджеров, которые не являются реальной задачей проекта, а именно манипулирование файлами проекта.
Обновлять: Мне также нужно иметь возможность вызывать методы P и PW.Итак, если у P есть метод a() и PW в качестве вызова метода b(), то я мог бы:
PW p = c.GetP();
p.a();
p.b();
По сути, это делается для того, чтобы сделать преобразование прозрачным.
Решение
Если вы хотите, чтобы эта часть скомпилировалась:
// ...
P p = c.GetP( ); // valid
PW p = c.GetP( ); // invalid
PR p = c.GetP( ); // invalid
// ...
Вам нужно уметь конструировать/конвертировать P в PW или PR.Вам нужно сделать что-то вроде этого:
class PW : public P
{
PW(const P &);
// more stuff
};
class PR : public P
{
PR(const P &);
// more stuff
};
Или вы имели в виду что-то вроде:
class P
{
operator PW() const;
operator PR() const;
// stuff
};
Другие советы
Вы пытаетесь использовать реальные переменные, а не указатели.Для этого потребуется актерский состав.Однако, если ваше определение класса выглядело так:
class C
{
public:
P* GetP() const { return p; }
private:
P* p;
}
Тогда, независимо от того, был ли p указателем на P, PW или PR, ваша функция не изменится, и любые (виртуальные) функции, вызываемые на P*, возвращаемом функцией, будут использовать реализацию P, PW или PR. в зависимости от того, каким был член p..
Я думаю, главное, что нужно помнить, это Принцип замены Лискова.Поскольку PW и PR являются подклассами P, их можно рассматривать как Ps.Однако военнопленных нельзя рассматривать как ПР, и наоборот.
В приведенном выше коде вы имеете противоположность нарезка проблема.
Вы пытаетесь присвоить P PW или PR, которые содержат больше информации, чем исходный объект.Как ты делаешь это?Скажем, P имеет только 3 переменных-члена, но PW имеет 12 дополнительных членов - откуда берутся их значения, когда вы пишете PW p = c.GetP()
?
Если это задание действительно является действительный, что действительно должно указывать на какую-то странность дизайна, тогда я бы реализовал PW::operator=(const P&)
и PR::operator=(const P&)
, PW::PW(const P&)
и PR::PR(const P&)
.Но в ту ночь я не спал слишком хорошо.
Это делает что-то разумное, учитывая, что все передается по значению.Не уверен, что это то, о чем вы подумали.
class P
{
public:
template <typename T>
operator T() const
{
T t;
static_cast<T&>(t) = *this;
return t;
}
};
Чтобы PW и PR можно было использовать через P, вам необходимо использовать ссылки (или указатели).Итак, вам действительно нужно изменить интерфейс C, чтобы он возвращал ссылку.
Основная проблема в старом коде заключалась в том, что вы копировали P в PW или PR.Это не сработает, поскольку PW и PR потенциально содержат больше информации, чем P, и с точки зрения типа объект типа P не является PW или PR.Хотя PW и PR оба являются P.
Измените код на это, и он скомпилируется:Если вы хотите возвращать различные объекты, производные от класса P во время выполнения, то класс C потенциально должен иметь возможность хранить все ожидаемые вами типы и быть специализированным во время выполнения.Поэтому в приведенном ниже классе я позволяю вам специализироваться, передавая указатель на объект, который будет возвращен по ссылке.Чтобы убедиться, что объект защищен от исключений, я заключил указатель в интеллектуальный указатель.
class C
{
public:
C(std::auto_ptr<P> x):
p(x)
{
if (p.get() == NULL) {throw BadInit;}
}
// Return a reference.
P& GetP() const { return *p; }
private:
// I use auto_ptr just as an example
// there are many different valid ways to do this.
// Once the object is correctly initialized p is always valid.
std::auto_ptr<P> p;
};
// ...
P& p = c.GetP( ); // valid
PW& p = dynamic_cast<PW>(c.GetP( )); // valid Throws exception if not PW
PR& p = dynamic_cast<PR>(c.GetP( )); // valid Thorws exception if not PR
// ...
Возможно, вы имеете в виду динамический_cast оператор?
Они не являются полностью взаимозаменяемыми.PW — это П.ПР — это П.Но P не обязательно PW и не обязательно PR.Вы можете использовать static_cast для приведения указателей от PW * к P * или от PR * к P *.Вы не должны использовать static_cast для приведения реальных объектов к их суперклассу из-за «нарезки».Э.г.если вы примените объект PW к P, лишние элементы в PW будут «отрезаны».Вы также не можете использовать static_cast для приведения от P* к PW*.Если вам действительно нужно это сделать, используйте динамический_cast, который во время выполнения проверит, действительно ли объект принадлежит к правильному подклассу, и выдаст вам ошибку во время выполнения, если это не так.
Я не уверен, что именно вы имеете в виду, но потерпите меня.
Они вроде уже есть.Просто назовите все P, и вы сможете притвориться, что PR и PW — это P.Однако PR и PW все же разные.
Если сделать все три эквивалентными, это приведет к проблемам с Принцип Лискова.Но тогда зачем давать им разные имена, если они действительно эквивалентны?
Второе и третье будут недействительными, поскольку это неявное преобразование вверх, а это опасная вещь в C++.Это связано с тем, что класс, к которому вы выполняете приведение, имеет больше функциональности, чем класс, который ему назначается, поэтому, если вы не примените его явно самостоятельно, компилятор C++ выдаст ошибку (по крайней мере, так должно быть).Конечно, это немного упрощает ситуацию (вы можете использовать RTTI для определенных вещей, которые могут относиться к тому, что вы хотите сделать, безопасно, не вызывая при этом гнев плохой объектности), но простота всегда является хорошим способом решения проблем.
Конечно, как указано в некоторых других решениях, эту проблему можно обойти, но я думаю, прежде чем пытаться обойти проблему, возможно, вам стоит переосмыслить дизайн.
Используйте ссылку или указатель на P, а не объект:
class C
{
public:
P* GetP() const { return p; }
private:
P* p;
};
Это позволит привязать PW* или PR* к C.p.Однако если вам нужно перейти от P к PW или PR, вам нужно использовать динамический_cast<PW*>(p), который вернет версию PW* p, или NULL p не является PW* (для например, потому что это пиар*).Однако Dynamic_cast имеет некоторые накладные расходы, и их лучше избегать, если это возможно (используйте виртуальные машины).
Вы также можете использовать оператор typeid() для определения типа объекта во время выполнения, но у него есть несколько проблем, в том числе то, что вы должны включить его, и что он не может обнаружить дополнительное происхождение.