2015-06-20 2 views
12

Я пытаюсь выполнить некоторые тесты с помощью {} -листов. Когда я собирал это в VS2015, выходКогда используется конструктор копирования для этой структуры?

copy A 0 

Просто не понимаю, где вызывается конструктор копирования?

#include <iostream> 

struct A 
{ 
    A() = default; 
    A(int i) : m_i(i) {} 
    A(const A& a) 
    { 
     std::cout << "copy 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) {} 
    A m_a; 
}; 

int main() 
{ 
    A a1{1}, a2{2}; 
    B b({ a1, a2 }); 
    return 0; 
} 
+0

Я получаю [без вывода] (http://ideone.com/JpwCdM). – emlai

+0

@zenith компилируется с '-fno-elide-constructors', если используется g ++/clang ++ – vsoftco

+8

' B b ({a1, a2}); 'почти эквивалентен' B b (B {a1, a2}); вероятно, происходит из 'B {a1, a2} .m_a' временного доступа к' b.m_a' – dyp

ответ

15

Короткая версия:

В прямой инициализации, как 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}).
+0

И вы видите копию ctor 'A', вызванную неявным движением ctor' B', поскольку конструктор перемещения 'A' удален, поскольку явные операции копирования отключены неявные движения. – vsoftco

+0

@vsoftco В VS2013 ни 'B', ни' A' не будет перемещаться ctor. Но ты прав по Стандарту. – dyp