Как вызвать конструктор копирования всех базовых классов для копирования объекта большинства производных классов при наследовании бриллиантов в С++?

Рассмотрим приведенный ниже код:

#include<iostream>
using namespace std;
class A
{
public:
     A() {cout << "1";}
     A(const A &obj) {cout << "2";}
};

class B: virtual A
{
public:
    B() {cout << "3";}
    B(const B & obj) {cout<< "4";}
};

class C: virtual A
{
public:
   C() {cout << "5";}
   C(const C & obj) {cout << "6";}
};

class D:B,C
{
public:
    D()  {cout << "7";}
    D(const D & obj) {cout << "8";}
};

int main()
{
   D d1;
   cout << "\n";
   D d(d1);
}

Вывод программы ниже:

1357
1358

Итак, для строки D d(d1) вызывается конструктор копирования класса D. Во время наследования нам нужно явно вызывать конструктор копирования базового класса, иначе вызывается только конструктор базового класса по умолчанию. Я понял до сих пор.

Моя проблема:

Теперь я хочу вызвать конструктор копирования всех базовых классов во время выполнения D d(d1). Для этого, если я попытаюсь ниже D(const D & obj) : A(obj), B(obj), C(obj) {cout << "8";}, я получаю эту ошибку: error: 'class A A::A' is inaccessible within this context

Как решить проблему. Мне нужен конструктор копирования A, B и C, когда вызывается конструктор копирования D. Это может быть очень небольшое изменение, но я не получаю.


comment
Это виртуальное наследование должно быть как минимум защищено. Прямо сейчас их частные. Пример: class B: virtual protected A   -  person WhozCraig    schedule 30.04.2017
comment
Как отметил @WhozCraig, после указания наследования public/protected теперь все работает: ideone.com/yjq3OG   -  person Failed Scientist    schedule 30.04.2017
comment
Хорошо, в классах С++ по умолчанию все наследования являются частными. Когда я сделал его общедоступным, например «класс B: виртуальный общедоступный A» и «класс C: виртуальный общедоступный A», тогда это сработало. Большое спасибо @WhozCraig за указание на это.   -  person Gaurav    schedule 30.04.2017
comment
Проблем с бриллиантами нет.   -  person curiousguy    schedule 07.05.2017


Ответы (4)


Во-первых, давайте изменим ваше наследование, поскольку в настоящее время оно является частным:

class B : virtual protected A {...};
class C : virtual protected A {...};

Теперь в конструкторе копирования явно укажите, что должны вызываться конструкторы копирования A, B и C:

class D : protected B, protected C {
    D(const D & obj) : A(obj), B(obj), C(obj) {cout << "8";}
};

И на выходе будет по желанию (2468).

Почему?

Когда у нас есть виртуальные базовые классы, они должны быть инициализированы самым производным классом, иначе возникнет двусмысленность относительно того, например, B или C отвечает за построение A.

§12.6.2, (13.1):

В конструкторе без делегирования инициализация выполняется в следующем порядке:

  • Во-первых, и только для конструктора самого производного класса (1.8), виртуальные базовые классы инициализируются в том порядке, в котором они появляются при обходе в глубину слева направо ориентированного ациклического графа базовых классов, где «лево- вправо» — это порядок появления базовых классов в списке базовых спецификаторов производного класса.

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

person donkopotamus    schedule 30.04.2017
comment
Я думаю, что единственным изменением, которое ему было нужно, была доступность наследования, часть 2 была правильной, как его конец (упомянутый ниже код) - person Failed Scientist; 30.04.2017
comment
Почему класс D должен знать об А? Конечно, это должны делать классы B/C, поэтому, если в будущем реализация этих двух классов изменится, вам не нужно будет изменять D. - person Ed Heal; 30.04.2017
comment
@EdHeal Если вы переопределяете конструктор копирования по умолчанию, я боюсь, что вы должны сообщить D о A, когда есть виртуальное наследование. - person donkopotamus; 30.04.2017
comment
Почему бы не заставить B вызывать конструктор копирования A? То же самое для C? - person Ed Heal; 30.04.2017
comment
@EdHeal Я немного раскрываю ответ, но что, если B вызывает создание A с 1, а C вызывает построение A с 2 (и при условии, что у A есть конструктор, принимающий int, а 1 vs 2 означает что-то).... какой из них вызывается? Вы можете построить A только один раз... так что же получится, а что нет? (Помните, что есть только один экземпляр A в D....) - person Andre Kostur; 01.05.2017
comment
Почему класс D должен знать об A? Потому что это виртуальный базовый класс, а не косвенный базовый класс. Многие пути доступа являются косвенными для целей управления доступом, но производный класс имеет прямую семантическую связь со своими виртуальными базовыми классами точно так же, как он имеет связь со своей унаследованной виртуальной функцией. Виртуальность базового класса имеет много общего с виртуальностью функций. - person curiousguy; 08.05.2017

Как вы унаследовали свои классы, все они используют наследование private.

Изменив наследование B от A и C от A на protected или public, вы можете решить проблему.

class B : protected virtual A
{
   ...
}

class C : protected virtual A
{
   ...
}

or

class B : public virtual A
{
   ...
}

class C : public virtual A
{
   ...
}

а затем обновите конструктор копирования D до:

D(const D & obj) : A(obj), B(obj), C(obj) {cout <<"8";}

PS Меня смущает, что конструктор по умолчанию работает даже с наследованием private.

person R Sahu    schedule 30.04.2017
comment
@RSahu В случае конструктора D::D() на самом деле не обращается к A::A(). Должен быть вызван конструктор для A, а D::D() не имеет права голоса, в каком именно конструкторе. - person donkopotamus; 30.04.2017
comment
@donkopotamus, не могли бы вы поделиться ссылкой, где объясняется, почему A::A() вызывается даже при частном наследовании? - person Gaurav; 01.05.2017
comment
@donkopotamus Должен быть вызван конструктор для A, а D::D() не имеет права голоса, в каком конструкторе что? - person curiousguy; 07.05.2017

Альтернативное решение, не требующее изменения модификаторов наследования класса B или C:

class A
{
public:
     A() {cout << "1";}
     A(const A &obj) {cout << "2";}
};

class B: virtual A
{
public:
    B() {cout << "3";}
    B(const B & obj) {cout<< "4";}
};

class C: virtual A
{
public:
   C() {cout << "5";}
   C(const C & obj) {cout << "6";}
};

class D:B,C,virtual A
{
public:
    D() {cout << "7";}
    D(const D & obj) : A(obj), B(obj), C(obj) {cout << "8";}
};
person Ben Voigt    schedule 07.05.2017

Относительно проверки доступа для конструкторов: из [class.access]/6

Все элементы управления доступом в пункте [class.access] влияют на возможность доступа к имени члена класса из объявления конкретной сущности... [ Примечание: этот доступ также применяется к неявным ссылкам на конструкторы, преобразованию функции и деструкторы. — конец примечания]

аналогично [class.access]/4

Специальные функции-члены подчиняются обычным правилам доступа. [ Пример: объявление конструктора protected гарантирует, что только производные классы и друзья могут создавать объекты с его помощью. — конечный пример ]

Относительно инициализации подобъекта базового класса: from [class.base.init]/9< /а>

В конструкторе без делегирования, если данный потенциально сконструированный подобъект не обозначен идентификатором mem-initializer-id (включая случай, когда нет списка mem-initializer-list, потому что у конструктора нет ctor-initializer), тогда... в противном случае объект инициализируется по умолчанию

Отсутствие какого-либо ctor-initializer для подобъекта базового класса означает, что подобъект инициализируется по умолчанию; из [dcl.init]/7

Инициализация по умолчанию объекта типа T означает: ... Выбранный таким образом конструктор вызывается с пустым списком аргументов для инициализации объекта.

Таким образом, отсутствие какой-либо базы в ctor-initializer является запросом на инициализацию по умолчанию для этой базы, что означает вызов конструктора по умолчанию.

Отсутствие упоминания базового класса не имеет значения; в любом случае конструктор не имеет имени и не назван в ctor-initializer, на него ссылаются либо явно, либо неявно. В стандарте ничего не говорится о том, что контроль доступа не должен выполняться в таком случае.

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

В любом случае можно изменить наследование с приватного на защищенное, даже добавить путь к виртуальному базовому классу:

class D: B, C, virtual A
{

Таким образом, виртуальный базовый класс A остается приватным, но доступен для D.

person curiousguy    schedule 08.05.2017