Почему инициализация фигурных скобок С++ 11 не работает в списке инициализации конструктора, когда инициализация скобок работает?

Чем инициализация {} в списке инициализации конструктора отличается от инициализации () при инициализации ссылки на абстрактные типы? Возьмите класс Bar ниже:

class AbstractBase
{
public:
    AbstractBase() {}
    virtual ~AbstractBase() = default;

    virtual void ab() = 0;
};

class Foo : public AbstractBase
{
public:
    Foo() {}

    void ab() {}
};

class Bar
{
public:
    Bar(const AbstractBase& base) : myBase{base} {}

private:
    const AbstractBase& myBase;
};


int main()
{
    Foo f{};
    Bar b{f};

}

При компиляции получаю ошибку

test5.cpp: In constructor ‘Bar::Bar(const AbstractBase&)’:
test5.cpp:22:48: error: cannot allocate an object of abstract type ‘AbstractBase’
     Bar(const AbstractBase& base) : myBase{base}
                                                ^
test5.cpp:2:7: note:   because the following virtual functions are pure within ‘AbstractBase’:
 class AbstractBase
       ^
test5.cpp:8:18: note:   virtual void AbstractBase::ab()
     virtual void ab() = 0;

Изменение линии

Bar(const AbstractBase& base) : myBase(base) {}

он компилируется и работает нормально.

Прочитав книгу Страуструпа по C++11, у меня сложилось впечатление, что {} было тем же, что и () в большинстве случаев, за исключением случаев, когда существовала двусмысленность между конструкторами, принимающими std::initializer_list‹>, и другими конструкторами, а также случаи где используется auto в качестве типа, ни то, ни другое я здесь не делаю.


person oryan_dunn    schedule 29.10.2014    source источник
comment
Мое практическое правило: используйте {} для списков элементов (включая ноль) и () для явного вызова любого другого конструктора.   -  person Mooing Duck    schedule 30.10.2014
comment
На самом деле это проблема, связанная с инициализацией списка ссылок.   -  person M.M    schedule 30.10.2014
comment
Та же проблема, что и здесь - stackoverflow.com/questions/19347004/   -  person Rudolfs Bundulis    schedule 30.10.2014
comment
@RudolfsBundulis спасибо за эту находку, мне было интересно, почему мой образец кода не смог даже скомпилироваться без S() {}!   -  person M.M    schedule 30.10.2014
comment
@MattMcNabb да, понял разницу из твоего ответа, недостаточно глубоко обдумал   -  person Rudolfs Bundulis    schedule 30.10.2014
comment
Еще ближе версия той же проблемы: stackoverflow.com/questions/24794598/   -  person M.M    schedule 30.10.2014


Ответы (1)


Краткий ответ: это была ошибка в стандарте, которая исправлена ​​в C++ 14, а в g++ 4.9 есть исправление (задним числом примененное и к режиму C++ 11). Отчет о дефекте 1288


Вот более простой пример:

struct S
{
    int x;
    S() { }     // this causes S to not be an aggregate (otherwise aggregate 
                // initialization is used instead of list initialization)
};

S x = 5;
S const &y { x } ;    

x = 6;
std::cout << y << std::endl;     // output : 5

В тексте C++11 значение S const &y {x}; не в том, чтобы связать y с x; на самом деле смысл в том, чтобы создать временную и привязать к ней ссылку. Из С++ 11 [dcl.init.ref]/3:

В противном случае, если T является ссылочным типом, временное значение prvalue типа, на которое ссылается T, инициализируется списком, и ссылка привязывается к этому временному типу. [Примечание: как обычно, привязка завершится ошибкой, и программа будет неправильно сформирована, если ссылочный тип является ссылкой lvalue на неконстантный тип. -конец примечания]

Это довольно глупо, ясно, что целью этого кода является привязка y непосредственно к x. В C++14 текст был изменен:

В противном случае, если в списке инициализаторов есть один элемент типа E и либо T не является ссылочным типом, либо его ссылочный тип связан со ссылкой на E, объект или ссылка инициализируется из этого элемента;

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


Ваше сообщение об ошибке исходит от компилятора, следующего за формулировкой С++ 11 и пытающегося создать временное из base для привязки ссылки к; и это не удается, потому что base имеет абстрактный тип.

person M.M    schedule 29.10.2014
comment
Если я удаляю const из своей ссылки при использовании инициализации {}, я получаю другую ошибку: test5.cpp: In constructor ‘Bar::Bar(AbstractBase&)’: test5.cpp:22:42: error: invalid initialization of non-const reference of type ‘AbstractBase&’ from an rvalue of type ‘<brace-enclosed initializer list>’ Bar(AbstractBase& base) : myBase{base} {} ^ - person oryan_dunn; 30.10.2014
comment
@oryan_dunn, который пытается привязать временную ссылку к неконстантной ссылке - person M.M; 30.10.2014
comment
да, я понимаю, что теперь он пытается сделать временный объект абстрактного типа. Я предполагаю, что до тех пор, пока не появится С++ 14, мне придется продолжать использовать () во всех моих списках инициализации конструктора, чтобы оставаться согласованным. - person oryan_dunn; 30.10.2014
comment
Нет, вам не нужно ждать C++14, вы можете просто использовать более новый компилятор, реализующий DR1288. - person Jonathan Wakely; 30.10.2014
comment
Я вижу, что GCC 4.9 должен это исправить, но я застрял на 4.8.2 через RHEL 6.5 и devtoolset 2.1. - person oryan_dunn; 30.10.2014