Как изглежда дефиницията (тялото) на наследен конструктор?

От моя прочит на отговорите на SO и връзката cppreference

Наследените конструктори са еквивалентни на дефинираните от потребителя конструктори с празно тяло и със списък с инициализатор на членове, състоящ се от един спецификатор на вложено име, който препраща всички свои аргументи към конструктора на базовия клас.

Бях заключил, че класовете по-долу D и E трябва да се държат еднакво.

#include <string>
#include <utility>
using namespace std;

class B
{
public:
  B(string&& a) : a(move(a))
  {
  }

  string a;
};

class D : public B
{
public:
  using B::B;
};

class E : public B
{
public:
  E(string&& a) : B(a)
  {
  }
};

string foo()
{
  return "bar";
}

int main()
{
  D d = foo();//This compiles
  E e = foo();//This does not compile
  return 0;
}

E e = foo() правилно не успява да компилира, тъй като конструкторът на B приема само string&&. Въпреки това D d = foo() минава добре. Защо така? Използваният компилатор е clang3.5.

РЕДАКТИРАНЕ: Освен това, както е обяснено в този отговор, идеалният идиом за пренасочване не е заместител на наследяващите конструктори. И така, как точно изглежда тялото?


person Pradhan    schedule 15.01.2015    source източник


Отговори (2)


Въпреки това D d = foo() преминава добре. Защо така?

Тъй като using B::B ефективно предава временния низ направо към конструктора на B, където той все още може да бъде обвързан с && (т.е. неговата стойностна категория все още е xvalue), тогава всички допълнителни извлечени- инициализация на клас (ако имаше други членове на данни, VDT и т.н.). Това е много желателно, тъй като целта на използването на конструкторите на базовия клас е да се позволи същото използване на клиента.

(Това контрастира с E(string&&), вътре в който посоченият параметър a вече не се счита за xvalue (изтичащ временно), узрял за преминаване към B::B.)

(Ако все още не сте го направили, може да искате да погледнете (std::forward)[http://en.cppreference.com/w/cpp/utility/forward] също... помага за перфектното препращане на аргументи)

person Tony Delroy    schedule 15.01.2015
comment
Как би изглеждала дефиницията обаче? Въпросът имаше само един пример. Но какво ще стане, ако, да речем, D има други членове с конструктори по подразбиране? Когато връзката cppreference казва напред, това всъщност предполага ли използването на универсалната препратка + std::forward идиома? Ако е така, как ще заобиколи проблемите, описани тук? - person Pradhan; 15.01.2015
comment
Как би изглеждала дефиницията обаче? - не би... всяко умствено приближение, което правите, е погрешно, както е обяснено в проблемите, очертани тук, връзката, която току-що публикувахте, където се споменава проблемът с яснота на конструкторите. Вместо да се опитвате мислено да моделирате производния конструктор като C++ код, извикващ деструктора на базовия клас, предлагам да мислите за открития конструктор на базовия клас като достъпен чрез интерфейса на производния клас с някои потенциални скрити пред- и/или след-стъпки за инициализиране на други бази и членове, VDT и др. - person Tony Delroy; 15.01.2015

Формулировката от стандарта за това как изглежда дефиницията на наследения конструктор в производния клас е малко по-ясна от тази, за която се споменава в описанието на cppreference, но ключовата фраза в последното е препраща всички свои аргументи< /em>. С други думи, стойностната категория на аргументите се запазва, което вашата дефиниция на E не прави и следователно не успява да се компилира.

От N3337, §12.9/8 [class.inhctor]

... Неявно дефиниран наследяващ конструктор изпълнява набора от инициализации на класа, които биха били извършени от написан от потребителя вграден конструктор за този клас с mem-initializer-list, чийто единствен mem- инициализатор има mem-initializer-id, който наименува базовия клас, обозначен в спецификатора на вложеното име на using-декларацията и списък с изрази, както е посочено по-долу, и където съставният израз в тялото на функцията е празен (12.6.2). Ако този написан от потребителя конструктор е неправилно оформен, програмата е неправилно оформена. Всеки израз в списъка с изрази е във формата static_cast<T&&>(p), където p е името на съответния параметър на конструктора, а T е декларираният тип на p.

Така че аргументите на конструктора се препращат перфектно към съответния наследен конструктор (std::forward (§20.2.3) е указано да връща static_cast<T&&>(p), точно същото като описанието по-горе). В зависимост от декларирания тип на параметъра на конструктора, свиването на препратката се извършва, както е описано в този отговор.

Във вашия случай [T=string&&] и предаването отново дава string&&, което може да се свърже с параметъра на B. За да съответства на това поведение в E, трябва да пренапишете конструктора като

E(string&& a) : B(static_cast<string&&>(a))
{
}
person Praetorian    schedule 15.01.2015
comment
Съжалявам, ако пропускам нещо очевидно, но подчертаната част от стандартния цитат звучи идентично с подхода, който връзката във въпроса смята за недостатъчна. - person Pradhan; 15.01.2015
comment
@Pradhan Недостатъчно е, ако следвате това описание и внедрите препращащия конструктор, ще трябва да го маркирате explicit ръчно, за разлика от използващата декларация, която наследява изричността, ако има такава, на базовия конструктор. Стандартът не разглежда това, когато сравнява поведението на наследения конструктор с това на предоставен от потребителя такъв. - person Praetorian; 15.01.2015