2015-03-21 5 views
2

Если вы посмотрите на код ниже, вы увидите, что существуют классы A, B и C, каждый из которых наследуется от последнего. Наследование - virtual, что означает, что C должен вызвать конструктор A, хотя он не наследует напрямую от A.Виртуальное наследование, конструкторы по умолчанию и дополнительное копирование

Это все хорошо и хорошо, но есть два способа справиться с этим, и они оба кажутся мне взломанными, поэтому я хотел бы знать, действительно ли это так, как вы должны это делать.

Первый способ (код ниже, как это), как представляется, называют A::A(a) дважды, один раз в B «конструктор s, а затем снова в C» конструктор s. Конечно, он не создается дважды, но побочным эффектом этого является то, что параметр a получает дополнительное время, просто чтобы он мог быть передан в B::B(a, b), хотя эта функция не закончила использование значения. Ему нужно только значение для построения A::A(a), но здесь этот конструктор вызывается от C, поэтому копия a, переданная в B::B(a, b), просто выброшена.

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

#include <iostream> 

struct Data { 
    Data() { 
     std::cout << "Creating data\n"; 
    } 
    Data(const Data& d) { 
     std::cout << "Copying data\n"; 
    } 
}; 

struct A { 
    A(Data a) 
    { 
     std::cout << "A(a)\n"; 
    } 
/* 
    A() 
    { 
     std::cout << "A()\n"; 
    } 
*/ 
}; 

struct B: virtual public A { 
    B(Data a, Data b) 
     : A(a) 
    { 
     std::cout << "B(a, b)\n"; 
    } 
/* 
    B(Data b) 
    { 
     std::cout << "B(b)\n"; 
    } 
*/ 
}; 

struct C: virtual public B { 
    C(Data a, Data b, Data c) 
     : A(a), 
      B(a, b) 
/* 
      B(b) 
*/ 
    { 
     std::cout << "C(a, b, c)\n"; 
    } 
}; 

int main(void) 
{ 
    Data a, b, c; 
    std::cout << "-- B --\n"; 
    B bb(a, b); 
    std::cout << "-- C --\n"; 
    C cc(a, b, c); 

    return 0; 
} 

EDIT: Просто чтобы прояснить, несколько человек сделал комментарий, что я должен просто пройти по ссылке. Игнорируя тот факт, что это не всегда то, что вы хотите (думайте, умные указатели), оно не устраняет тот факт, что вы передаете значение функции, которая никогда не используется, что кажется изворотливым. Таким образом, выбор - либо передать значение, которое выбрасывается тихо (и надеется, что вы его никогда не укусите), либо реализовать функцию, которую вы никогда не позвоните (и надейтесь, что никогда не вернется, чтобы укусить.)

Итак, мой оригинал вопрос все еще стоит - действительно ли это единственные два способа сделать это? Вы должны выбирать между избыточной переменной или избыточной функцией?

+4

Просто передайте аргументы по ссылке. –

+0

привет Malvineous, мне интересно, почему вы не принимаете ответ Матеуша? – niceman

+2

@niceman Не все патрулируют сайт во все времена.У ОП есть 3k rep и 62 вопроса, большинство из них с принятым ответом. Вы можете подумать, что они знают, как и когда принимать. – Angew

ответ

2

Я не уверен, если это было вашим намерением, но вы делаете много ненужных копий Data, потому что каждый параметр передается по значению. Это означает, что каждый раз вызывается A::A(a), создается новый экземпляр Data, который копируется из параметра a. В B::B(a,b) сделаны две такие копии и в C::C(a,b,c) - три экземпляра.Но обратите внимание, что B::B(a,b) звонки A::A(a) и C::C(a,b,c) звонки B::B(a,b), так что я думаю, что вызов C::C(a,b,c) фактически создает шесть копий Data:

  • три копии a
  • два копий b
  • один копия c

Вы можете избежать этого, передав аргументы конструктора по ссылке - тогда никакая копия не будет сделана, если вы в купе-конструкторе или списке инициализации не решите его создать.

Попробуйте это:

#include <iostream> 

struct Data { 
    Data() { 
     std::cout << "Creating data\n"; 
    } 
    Data(const Data& d) { 
     std::cout << "Copying data\n"; 
    } 
}; 

struct A { 
    A(const Data& a) 
    { 
     std::cout << "A(a)\n"; 
    } 
/* 
    A() 
    { 
     std::cout << "A()\n"; 
    } 
*/ 
}; 

struct B: virtual public A { 
    B(const Data& a, const Data& b) 
     : A(a) 
    { 
     std::cout << "B(a, b)\n"; 
    } 
/* 
    B(Data b) 
    { 
     std::cout << "B(b)\n"; 
    } 
*/ 
}; 

struct C: virtual public B { 
    C(const Data& a, const Data& b, const Data& c) 
     : A(a), 
      B(a, b) 
/* 
      B(b) 
*/ 
    { 
     std::cout << "C(a, b, c)\n"; 
    } 
}; 

int main(void) 
{ 
    Data a, b, c; 
    std::cout << "-- B --\n"; 
    B bb(a, b); 
    std::cout << "-- C --\n"; 
    C cc(a, b, c); 

    return 0; 
} 

Выход:

Creating data 
Creating data 
Creating data 
-- B -- 
A(a) 
B(a, b) 
-- C -- 
A(a) 
B(a, b) 
C(a, b, c) 

EDIT:

Я вижу, что я неправильно понял вашу проблему. Теперь я не уверен, понимаю ли я его вообще. После компиляции исходного кода:

[Выход]

[Cut] 
-- C -- 
Copying data // copy of c in C::C() 
Copying data // copy of b in C::C() 
Copying data // copy of a in C::C() 
Copying data // copy of a passed to A::A() 
A(a) 
Copying data // copy of b passed to B::B() 
Copying data // copy of a passed to B::B() 
B(a, b) 
C(a, b, c) 

Теперь, который копирует вы хотите, чтобы избавиться от?

Вы написали, что A::A() вызывается дважды - в B::B() и C::C(). Но вы создаете два объекта, один из которых B и один из типов C. Оба эти типа наследуют от A, поэтому A::A() всегда будет вызываться - явно или нет. Если вы не вызываете A::A() явно, будет вызываться конструктор по умолчанию для A. Если он не определен, будет генерироваться ошибка компилятора (попробуйте удалить вызов B(a, b) с C::C()).

+0

Извините, возможно, я не был в курсе моего вопроса. Я не перешел по ссылке, чтобы было ясно, что копии были. Я отредактировал вопрос соответствующим образом. Даже если вы измените его на использование ссылок, как это было в вашем коде, вы все еще вызываете 'B (a, b)' в конструкторе 'C', но это значение' a' никогда не используется и не выбрасывается. Что, если это был 'std :: unique_ptr' - вы не можете передать его двум различным функциям! – Malvineous

+0

См. Обновленный ответ. –

+0

Простите, что трудно понять, что я имею в виду. Копия, которую я хочу избавиться, - это «копия переданного в B :: B()», но проблема не обязательно в самой копии, копия является проявлением основной проблемы, а именно: «a» передается обоим 'A :: A()' и 'B :: B()', когда на самом деле это нужен только одному из этих конструкторов. Но я должен передать его обоим конструкторам, и один просто игнорирует его. Для меня это выглядит плохой дизайн, что заставляет меня думать, что я делаю что-то неправильно. – Malvineous

1

Способ использования виртуального наследования не так, и в этом вопросе - не существует никакой связи между «дополнительных копий» и виртуальное наследование

Виртуальное наследование делается для того, чтобы решить эту проблему Даймонд - если оба лошадь и Птица наследуется от «Животного», а Пегас наследует как от Лошади, так и от Птицы - мы не хотим, чтобы каждая переменная члена в «Животном» содержалась дважды. Если мы наследуем Лошадь и Птицу практически для того, чтобы иметь объект животного только один раз в Pegasus.

здесь, этот пример не представляет проблему алмазной поэтому не отражают реальное использование виртуального наследования (другие тогда это вынуждает CTOR называться первым.)

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

+0

Первые два абзаца ошибочны. OP использует виртуальное наследование, поэтому существует виртуальное наследование, период. Да, VI обычно используется для предотвращения проблем с алмазами, но независимо от того, является ли наследование виртуальным или нет, зависит исключительно от используемого ключевого слова 'virtual'. Алмаз или алмаз не влияет на виртуальность наследования. – Angew

+0

, конечно, компилятор компилирует все как виртуальное наследование. Дело в том, что концептуально нет никакой связи между «дополнительными копиями» и фактом, что наследование является виртуальным, а также, я пытаюсь упомянуть, что этот пример не использует виртуальное наследование, поскольку оно должно использоваться –

+0

Я изменил ответ немного –