C++, виртуальное наследование, странный абстрактный класс + проблема с клонированием

Извините за большой объем исходного кода. Есть три абстрактных класса P, L, PL. Третий класс PL является производным от классов P и L с использованием виртуального наследования:

template <typename T>  //Abstract class
class P
{

    public:
            virtual ~P() = 0;
    virtual P <T> *clone() const  = 0;
};

template <typename T>
P<T>::~P() {}

template <typename T>
P<T> * P <T>:: clone() const { return new P <T> ( *this ); }

template <typename T>  //Abstract class
class L
{
    public:
            virtual ~L() = 0;
    virtual L <T> *clone() const = 0;
};

template <typename T>
L<T>::~L() {}

template <typename T>
L<T> *L <T> ::clone() const { return new L <T> ( *this );}

template <typename T>
class PL: virtual public P <T>, virtual public L <T>  //Abstract class
{
    public:
            PL() : P <T>(), L<T>() {}
    virtual ~PL() = 0;
    virtual PL <T> *clone() const  = 0;
};

template <typename T>
PL<T>::~PL() {}

template <typename T>
PL<T> * PL <T> :: clone() const { return new PL <T> ( *this );}

Каждый класс имеет собственную реализацию метода клонирования.

Два следующих класса PA, PC являются производными от класса P с использованием виртуального наследования:

template <typename T>
class PC : virtual public P <T>
{
    public:
            PC() : P <T> () {}
            virtual ~PC() {}
            virtual PC <T> *clone() const {return new PC <T> ( *this );}
};

template <typename T>
class PA : virtual public P <T>
{
    public:
            PA() : P <T> () {}
            virtual ~PA() {}
            virtual PA <T> *clone() const {return new PA <T> ( *this );}
};

Последние два класса PCL и PAL получены с использованием виртуального наследования от PC и PL, PA и PL.

template <typename T>
class PCL : public PC <T>, public PL <T>
{
    public:
            PCL() : P <T> (), PC <T> (), PL <T> ()  {}
            virtual ~PCL() {}
            virtual PCL <T> *clone() const {return new PCL <T> ( *this );}
};

template <typename T>
class PAL : public PA <T>, public PL <T>
{
    public:
            PAL() : P <T> (), PA <T> (), PL <T> () {}
            virtual ~PAL() {}
            virtual PAL <T> *clone() const {return new PAL <T> ( *this );}

};

Вот схема зависимостей классов:

.......... P .... L.....
........../|\..../......
........./.|.\../.......
......../..|..\/........
.......PC..PA..PL.......
.......|...|.../|.......
.......|...|../.|.......
.......|...PAL..|.......
.......|........|.......
.......PCL_____/........  

Пожалуйста, не обсуждайте это предложение :-))). У меня есть следующие 3 вопроса:

1) Правильно ли была переписана эта зависимость класса на C++ (прежде всего размещение слова «виртуальный»)?

2) Я не уверен, что не так, посмотрите код, пожалуйста:

int main(int argc, _TCHAR* argv[])
{
PCL <double> * pcl = new PCL <double>(); //Object of abstract class not allowed
PAL <double> * pal = new PAL <double>(); //Object of abstract class not allowed
PL <double> *pl1 = pcl->clone(); //Polymorphism
PL <double> *pl2 = pal->clone(); //Polymorphism
return 0;
} 

Невозможно создавать новые объекты классов PAL/PCL, оба класса отмечены как абстрактные. Но они не абстрактны. В чем проблема?

3) Можно ли использовать полиморфизм вместе с методом clone()? См. код выше, пожалуйста...

Спасибо за вашу помощь...


ОБНОВЛЕННЫЙ ВОПРОС

Я исправил код. Но появляется следующая ошибка при использовании компилятора VS 2010:

template <typename T>  //Abstract class
class P
{
    public:
    virtual ~P() = 0;
    virtual P <T> *clone() const  = 0;
};

template <typename T>
P<T>::~P() {}

template <typename T>
P<T> * P <T>:: clone() const { return new P <T> ( *this ); }


template <typename T>  //Abstract class
class L
{
    public:
    virtual ~L() = 0;
    virtual L <T> *clone() const = 0;
};

template <typename T>
L<T>::~L() {}

template <typename T>
L<T> *L <T> ::clone() const { return new L <T> ( *this );}


template <typename T>
class PL: virtual public P <T>, virtual public L <T>  //Abstract class
{
    public:
            PL() : P <T>(), L<T>() {}
    virtual ~PL() = 0;
    virtual PL <T> *clone() const  = 0; 
};

template <typename T>
PL<T>::~PL() {}

template <typename T>
PL<T> * PL <T> :: clone() const { return new PL <T> ( *this );}


template <typename T>
class PC : virtual public P <T>
{
    protected:
            T pc;
    public:
            PC() : P <T> () {}
    virtual ~PC() {}
            virtual PC <T> *clone() const {return new PC <T> ( *this );}
};

template <typename T>
class PA : virtual public P <T>
{
    public:
            PA() : P <T> () {}
    virtual ~PA() {}
            virtual PA <T> *clone() const {return new PA <T> ( *this );}
};

template <typename T>
class PCL : public PC <T>, public PL <T>
{
public:
            PCL() : P <T> (), PC <T> (), PL <T> () {}
            virtual ~PCL() {}
            virtual PCL <T> *clone() const {return new PCL <T> ( *this   );}
}; //Error using VS 2010: Error 1   error C2250: 'PCL<T>' : ambiguous inheritance of 'PC<T> *P<T>::clone(void) const'


template <typename T>
class PAL : public PA <T>, public PL <T>
{
public:
            PAL() : P <T> (), PA <T> (), PL <T> ()  {}
            virtual ~PAL() {}
            virtual PAL <T> *clone() const {return new PAL <T> ( *this );}
}; //Error VS 2010: Error   1   error C2250: 'PAL<T>' : ambiguous inheritance of 'PA<T> *P<T>::clone(void) const'


int main(int argc, char* argv[])
{
PCL <double> * pcl = new PCL <double>();
PAL <double> * pal = new PAL <double>();
PL <double> *pl1 = pcl->clone();
PL <double> *pl2 = pal->clone();
return 0;
}

Может быть, я что-то упустил... Но g++ компилирует этот код нормально.


person Johnas    schedule 03.08.2011    source источник
comment
Есть ли у ваших чистых виртуальных деструкторов в P, L и PL реализация по умолчанию?   -  person Chad    schedule 03.08.2011
comment
Я думаю, что вы не можете добавлять определения к объявлениям чистых функций; скажи = 0;. Также отсутствуют круглые скобки после PL <T> { } (должно быть PL <T>() { }). Как говорит @Chad, вам не нужно делать деструкторы чисто виртуальными, если у вас уже есть другие чисто виртуальные функции.   -  person Kerrek SB    schedule 03.08.2011
comment
@Chad: Да, я исправил код...   -  person Johnas    schedule 03.08.2011


Ответы (5)


Это компилируется для меня с использованием VC10:

template <typename T>  //Abstract class
class P
{
public:
    virtual ~P() = 0;
    virtual P <T> *clone() const  = 0;
};

template <typename T>
P<T>::~P() {}


template <typename T>  //Abstract class
class L
{
public:
    virtual ~L() = 0;
    virtual L <T> *clone() const = 0;
};

template <typename T>
L<T>::~L() {}


template <typename T>
class PL: virtual public P <T>, virtual public L <T>  //Abstract class
{
public:
    PL() : P <T>(), L<T>() {}
    virtual ~PL() = 0;
//  virtual PL <T> *clone() const  = 0; // REMOVED!
};

template <typename T>
PL<T>::~PL() {}


template <typename T>
class PC : virtual public P <T>
{
protected:
    T pc;
public:
    PC() : P <T> () {}
    virtual ~PC() {}
    virtual PC <T> *clone() const {return new PC <T> ( *this );}
};

template <typename T>
class PA : virtual public P <T>
{
public:
    PA() : P <T> () {}
    virtual ~PA() {}
    virtual PA <T> *clone() const {return new PA <T> ( *this );}
};

template <typename T>
class PCL : public PC <T>, public PL <T>
{
public:
    PCL() : P <T> (), PC <T> (), PL <T> () {}
    virtual ~PCL() {}
    virtual PCL <T> *clone() const {return new PCL <T> ( *this   );}
};


template <typename T>
class PAL : public PA <T>, public PL <T>
{
public:
    PAL() : P <T> (), PA <T> (), PL <T> ()  {}
    virtual ~PAL() {}
    virtual PAL <T> *clone() const {return new PAL <T> ( *this );}
};


int main()
{
    PCL <double> * pcl = new PCL <double>();
    PAL <double> * pal = new PAL <double>();
    PL <double> *pl1 = pcl->clone();
    PL <double> *pl2 = pal->clone();
    return 0;
}

Обратите внимание, что мне пришлось удалить PL <T> *PL <T>::clone(), чтобы VC принял код. Проблема в том, что если у вас есть PL<T> и вы вызываете его clone(), он статически вернет L<T>*, а не PL<T>*, даже если динамический тип относится к классу, производному от PL<T>.

person sbi    schedule 03.08.2011
comment
Хорошо, это работает. Но вы удалили все определения методов клонирования в трех абстрактных классах. - person Johnas; 03.08.2011
comment
@Johnas: Эти методы пытались создать экземпляры объектов своего собственного абстрактного класса. Это точно никогда не сработает. Но они вам все равно не нужны, потому что у вас никогда не будет объектов этих классов (поскольку они абстрактны). У вас всегда будут только объекты производных классов, и они реализуют clone(). На самом деле, единственная проблема с кодом из моего ответа заключается в том, что я написал в своем ответе под кодом. - person sbi; 03.08.2011
comment
Я забыл, что могу создать указатель на абстрактный класс, но не на объект (ни динамически)... Спасибо за вашу помощь... - person Johnas; 03.08.2011

Просто некоторые небольшие ошибки в вашем коде:

  • Правильно установите базовые инициализаторы: PAL() : PC <T>(), PL <T>() {} нет инициализации P<T>, которая не является прямой базой; но[Извините, это было неправильно — вам нужно вызывать виртуальный базовый конструктор из-за виртуального наследования.] вам нужно произнести круглые скобки.

  • Объявить чистые виртуальные функции без определения: virtual L <T> *clone() const = 0;

  • Дважды проверьте, хотите ли вы, чтобы PL наследовал виртуально от P, но не виртуально от L (но это нормально).

  • Дважды проверьте, что все ваши clone() имеют одинаковую константность.

  • Если у вас уже есть одна чистая виртуальная функция в абстрактном базовом классе, вам не следует делать деструктор чистым. Вместо этого, скажем, virtual ~T() { }. В противном случае вы все равно должны предоставить определение чисто виртуального деструктора (поскольку деструктор всегда должен быть вызываемым): virtual T::~T() { }

person Kerrek SB    schedule 03.08.2011
comment
Действительно, чистая ошибка должна исходить из того, что clone() не является константой. ideone.com/3bTty – это исправленная версия кода, которая будет компилироваться (но не по ссылке). Поскольку в коде так много ошибок, сомнительно, действительно ли он соответствует коду, с которым у OP возникают проблемы. - person PlasmaHH; 03.08.2011
comment
На самом деле я не получил никаких предупреждений или ошибок относительно константности. Я не уверен на 100% прямо сейчас, но есть вероятность, что вместо переопределения происходит ошибочное скрытие. Я не хочу тратить на это больше времени сейчас, но очень важно правильно поставить подписи при переопределении. - person Kerrek SB; 03.08.2011
comment
Я бы не ожидал каких-либо предупреждений или ошибок, касающихся константности, это просто еще одна подпись, просто посмотрите, как если бы это была опечатка в имени функции. - person PlasmaHH; 03.08.2011
comment
Так в чем проблема? У меня все нормально компилируется, есть еще вопросы? - person Kerrek SB; 03.08.2011
comment
Я получил следующую ошибку: Ошибка 1 ошибка C2250: 'PCL‹T›': неоднозначное наследование 'PC‹T› *P‹T›::clone(void) const'... См. обновленный код, пожалуйста. .. ; - person Johnas; 03.08.2011
comment
Спасибо за терпеливость. Но другой проблемы не вижу, может я слепой :-). Этот код не компилируется с использованием MSVS, но нормально работает с g++.. - person Johnas; 03.08.2011
comment
Я неправильно понял конструкцию виртуальной базы, вы правы, что в инициализаторе P<T>(). Я не знаю, он отлично компилируется в GCC со всеми предупреждениями. Какая у вас версия MSVS? 2010SP1? - person Kerrek SB; 03.08.2011
comment
Я использую MSVS2010 sp1. Я публикую окончательную версию своего кода в обновленном вопросе ... Не могли бы вы проверить это, пожалуйста? - person Johnas; 03.08.2011
comment
Извините, у меня только GCC! Может быть, кто-то еще здесь мог бы взглянуть. - person Kerrek SB; 03.08.2011
comment
Хм, просто из любопытства, а не из-за того, что вам нужно, что произойдет, если вы сделаете PAL и PCL наследованием виртуально? - person Kerrek SB; 03.08.2011

MSVC не поддерживает ковариантные возвраты должным образом. Если вы думаете, что переопределяете функцию, на самом деле вы просто скрываете ее. Это проблема под рукой.

person Puppy    schedule 03.08.2011
comment
@Johnas: Можешь удалить PL <T> *PL <T>::clone()? Потому что тогда он компилируется для меня. (То есть, когда я удаляю реализацию чисто виртуальных clone() функций, которые пытаются создавать экземпляры абстрактных классов.) - person sbi; 03.08.2011
comment
Не могли бы вы дать мне совет, как изменить код, чтобы его можно было перевести с помощью MSVS 2010? - person Johnas; 03.08.2011
comment
@Johnas: Как я уже сказал, он скомпилируется для меня, если я удалю PL <T> *PL <T>::clone(). - person sbi; 03.08.2011
comment
Я отправил свой пост в тот же момент, что и вы :-) После удаления PL‹T› * PL ‹T› :: clone() const { return new PL ‹T› ( *this );} ничего не изменилось, та же ошибка ... - person Johnas; 03.08.2011
comment
@Johnas: удалите не только определение функции-члена, но и ее объявление. Это компилируется для меня. - person sbi; 03.08.2011
comment
@Johnas: см. мой ответ. - person sbi; 03.08.2011
comment
Извините, я не совсем понял, что вы имеете в виду... PL ‹T› *PL ‹T›::clone() является частью определения, объявление является виртуальным PL ‹T› *clone() const = 0 ... - person Johnas; 03.08.2011

Поскольку метод клонирования в вашем классе P является абстрактным, поскольку он определен

virtual P <T> *clone() const = 0 {};

(кстати, {} неверно). Что вы должны понимать, так это то, что после создания экземпляра этого шаблона это отдельный метод с полностью отличной сигнатурой от метода клонирования производных классов. создание экземпляра шаблона ведет себя так, как если бы он генерировал новый код. Таким образом, это чистый абстрактный метод, который так и не был реализован. Таким образом, каждый, кто наследует этот метод (каждый производный класс), становится абстрактным классом.


Редактировать: Что касается третьего вопроса. Полиморфизм времени выполнения и времени компиляции плохо сочетается. Я понятия не имею, с какой стати вы хотите использовать такую ​​сложную структуру, но я уверен, что дизайн можно значительно упростить.

person Osada Lakmal    schedule 03.08.2011
comment
Таким образом, это чистый абстрактный метод, который так и не был реализован, это не совсем правильно, поскольку эти функции не являются шаблонами-членами, их тип возвращаемого значения просто зависит от параметра шаблона шаблона класса, что вполне нормально. Кроме того, ковариантные возвраты реализаций производных классов в порядке, хотя их трудно обнаружить как таковые (что является одной из многих причин, по которым следует переосмыслить этот дизайн). - person PlasmaHH; 03.08.2011

  1. В вашем коде есть пара опечаток, которые нужно исправить, но virtual в решетке наследования верны. (Это минимум. Вы можете добавить больше; в тех редких случаях, когда встречаются такие решетки, зачастую лучше всего сделать все наследование виртуальным, чтобы защититься от будущей эволюции.)

  2. В вашем тестовом коде нет ничего плохого. Как только опечатки в оригинале исправлены, он отлично компилируется с g++.

  3. Это возможно, и это должно работать так, как вы это сделали.

person James Kanze    schedule 03.08.2011