Вопрос

В настоящее время я страдаю пердежом мозга.Я делал это раньше, но не могу вспомнить точный синтаксис и не могу просмотреть написанный мной код, поскольку в то время работал в другой компании.У меня есть такая договоренность:

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() для определения типа объекта во время выполнения, но у него есть несколько проблем, в том числе то, что вы должны включить его, и что он не может обнаружить дополнительное происхождение.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top