Короткая версия:
В прямой инициализации, как B b({a1, a2})
, то приготовился-INIT-лист{a1, a2}
рассматривается как один аргумент к конструктору B
. Этот аргумент {a1, a2}
будет использоваться для инициализации первого параметра конструктора. B
содержит неявно объявленный конструктор B(B const&)
. Ссылка B const&
может быть инициализирована от {a1, a2}
путем создания временных B
. Этот временной элемент содержит подобъект A
, и этот подобъект, наконец, будет скопирован в b.m_a
через конструктор копирования B(B const&)
.
Сравните:
void foo(B const& b0);
foo({a1, a2}); // one argument, creates a temporary `B`
Мы не увидим никакого копирования для инициализации формы B b{a1, a2}
ни B b(a1, a2)
, ни B b = {a1, a2}
, потому что эти случаи рассматривать a1
и a2
как (отдельные) аргументы - если жизнеспособный std::initializer_list
конструктор существует.
Длинная версия:
Класс B
содержит следующие конструкторы:
B(const A& a) : m_a(a) {} // #0
B(const A& a1, const A& a2) {} // #1
B(B const&) = default; // implicitly declared #2
B(B&&) = default; // implicitly declared #3
#3
не будет присутствовать в VS2013, из-за отсутствия поддержки неявно предусмотренных специальными функциями ходу , # 0 не используется в программе OP.
Инициализация B b({a1, a2})
должна выбрать один из этих конструкторов. Мы приводим только один аргумент {a1, a2}
, поэтому №1 нецелесообразен. # 0 также не является жизнеспособным, так как A
не может быть построен из двух аргументов. Оба # 2 и # 3 по-прежнему жизнеспособны (# 3 не существует в VS2013).
Теперь разрешение на перегрузку пытается инициализировать B const&
или B&&
от {a1, a2}
. Временный B
будет создан и привязан к этой ссылке. Разрешение перегрузки будет предпочтительнее от 3 до 2, если № 3 существует.
Создание временного снова смотрит на четырех конструкторов, показанных выше, но теперь у нас есть два аргументы a1
и a2
(или initializer_list
, но это не имеет значения здесь). # 1 - единственная жизнеспособная перегрузка, а временная - B(const A& a1, const A& a2)
.
Итак, мы по существу заканчиваем по существу B b(B{a1, a2})
.Копия (или перемещение) от временного B{a1, a2}
до b
может быть отклонено (копия-эл.). Вот почему g ++ и clang ++ не называют копией ctor, ни движением ctor ни B
, ни A
.
VS2013, похоже, не имеет возможности построить копию здесь, и он не может перемещать-построить, поскольку он не может неявно предоставить # 3 (VS2015 исправит это). Поэтому VS2013 вызывает B(B const&)
, который копирует B{a1, a2}.m_a
в b.m_a
. Это вызывает конструктор копирования A
.
Если # 3 существует, и движение не отклонено, вызывается конструктор # 3 с неявным объявлением. Поскольку A
имеет явно объявленный конструктор копирования, конструктор перемещения неявно объявляется для A
. Это также приводит к созданию копии от B{a1, a2}.m_a
до b.m_a
, но через ход ctor B
.
В VS2013, если вручную добавить перемещение CTOR к A
и B
, мы заметим, что A
будет перемещен вместо скопирована:
#include <iostream>
#include <utility>
struct A
{
A() = default;
A(int i) : m_i(i) {}
A(const A& a)
{
std::cout << "copy A " << m_i << std::endl;
}
A(A&& a)
{
std::cout << "move A " << m_i << std::endl;
}
int m_i = 0;
};
struct B
{
//B(const A& a) : m_a(a) {}
B(const A& a1, const A& a2) {}
B(B const&) = default;
B(B&& b) : m_a(std::move(b.m_a)) {}
A m_a;
};
Это, как правило, легче понять такие программы, отслеживая от каждый конструктор. Использование MSVC конкретных __FUNCSIG__
(г ++/лязг ++ может использовать __PRETTY_FUNCTION__
):
#include <iostream>
#define PRINT_FUNCSIG() { std::cout << __FUNCSIG__ << "\n"; }
struct A
{
A() PRINT_FUNCSIG()
A(int i) : m_i(i) PRINT_FUNCSIG()
A(const A& a) : m_i(a.m_i) PRINT_FUNCSIG()
int m_i = 0;
};
struct B
{
B(const A& a1, const A& a2) PRINT_FUNCSIG()
B(B const& b) : m_a(b.m_a) PRINT_FUNCSIG()
A m_a;
};
int main()
{
A a1{1}, a2{2};
B b({ a1, a2 });
return 0;
}
печатается (без замечаний):
__thiscall A::A(int) // a1{1}
__thiscall A::A(int) // a2{2}
__thiscall A::A(void) // B{a1, a2}.m_a, default-constructed
__thiscall B::B(const struct A &,const struct A &) // B{a1, a2}
__thiscall A::A(const struct A &) // b.m_a(B{a1, a2}.m_a)
__thiscall B::B(const struct B &) // b(B{a1, a2})
Дополнительные Factoids:
- Оба VS2015 и VS2013 do elide копия строительства
B b(B{a1, a2});
, но не оригинал B b({a1, a2})
.
Я получаю [без вывода] (http://ideone.com/JpwCdM). – emlai
@zenith компилируется с '-fno-elide-constructors', если используется g ++/clang ++ – vsoftco
' B b ({a1, a2}); 'почти эквивалентен' B b (B {a1, a2}); вероятно, происходит из 'B {a1, a2} .m_a' временного доступа к' b.m_a' – dyp