Странное поведение инициализации копирования, не вызывает конструктор копирования!

Я читал разницу между прямой инициализацией и инициализацией копирования (§8.5/12):

T x(a);  //direct-initialization
T y = a; //copy-initialization

Что я понял из чтения об инициализации копирования, так это о том, что для этого нужен доступный и неявный конструктор копирования, иначе программа не будет компилироваться. Я подтвердил это, написав следующий код:

struct A
{
   int i;
       A(int i) : i(i) { std::cout << " A(int i)" << std::endl; }
   private:
       A(const A &a)  {  std::cout << " A(const A &)" << std::endl; }
};

int main() {
        A a = 10; //error - copy-ctor is private!
}

GCC дает ошибку ( ideone), говоря:

prog.cpp: 8: ошибка: 'A::A(const A&)' является частной

Пока все в порядке, подтверждая, что Херб Саттер говорит,

Инициализация копирования означает, что объект инициализируется с использованием конструктора копирования после первого вызова пользовательского преобразования, если это необходимо, и эквивалентен форме "T t = u;":


После этого я сделал копию ctor доступной, комментируя private ключевое слово. Теперь, естественно, я ожидал, что будет напечатано следующее:

A (const A &)

Но, к моему удивлению, вместо этого он печатает ( ideone):

A (int i)

Зачем?

Alright, I understand that first a temporary object of type A is created out of 10 который int type, by using A(int i), applying the conversion rule as its needed here (§8.5/14), and then it was supposed to call copy-ctor to initialize a, Но это не так. Зачем?

If an implementation is permitted to eliminate the need to call copy-constructor (§8.5/14), then why is it not accepting the code when the copy-constructor is declared private? After all, its not calling it. Its like a spoiled kid who first irritatingly asks for a specific toy, and when you give him one, the specific one, he throws it away, behind your back.: |

Could this behavior be dangerous? I mean, I might do some other useful thing in the copy-ctor, but if it doesn't call it, then does it not alter the behavior of the program?

6 ответов

Решение

Вы спрашиваете, почему компилятор проверяет доступ? 12,8/14 в C++03:

Программа плохо сформирована, если неявно используется конструктор копирования или оператор присваивания копии для объекта и специальная функция-член недоступна

Когда реализация "опускает конструкцию копирования" (разрешено 12.8/15), я не верю, что это означает, что ctor копии больше не "неявно используется", он просто не выполняется.

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

Я уверен, что авторы сочли бы это плохим. Конечно, проще написать переносимый код таким способом - компилятор сообщит вам, если вы пишете код, который пытается скопировать не копируемый объект, даже если копия окажется в вашей реализации. Я подозреваю, что разработчикам также может быть неудобно выяснять, будет ли оптимизация успешной перед проверкой доступа (или отложить проверку доступа до попытки оптимизации), хотя я не знаю, заслуживает ли это внимания.

Может ли это поведение быть опасным? Я имею в виду, что я мог бы сделать кое-что полезное в copy-ctor, но если он не вызывает его, то не меняет ли это поведение программы?

Конечно, это может быть опасно - побочные эффекты в конструкторах копирования возникают тогда и только тогда, когда объект действительно копируется, и вы должны разработать их соответствующим образом: стандарт гласит, что копии могут быть исключены, поэтому не помещайте код в конструктор копирования, если только вы счастливы, что он будет допущен в условиях, определенных в 12.8/15:

MyObject(const MyObject &other) {
    std::cout << "copy " << (void*)(&other) << " to " << (void*)this << "\n"; // OK
    std::cout << "object returned from function\n"; // dangerous: if the copy is
      // elided then an object will be returned but you won't see the message.
}

C++ явно допускает несколько оптимизаций с использованием конструктора копирования, которые фактически изменяют семантику программы. (Это в отличие от большинства оптимизаций, которые не влияют на семантику программы). В частности, есть несколько случаев, когда компилятору разрешается повторно использовать существующий объект, а не копировать его, если он знает, что существующий объект станет недоступным. Это (копирование конструкции) является одним из таких случаев; другой подобный случай - "оптимизация возвращаемого значения" (RVO), где, если вы объявляете переменную, которая содержит возвращаемое значение функции, тогда C++ может выбрать для размещения это в кадре вызывающей стороны, так что это не нужно чтобы скопировать его обратно вызывающей стороне, когда функция завершится.

В общем, в C++ вы играете с огнем, если вы определяете конструктор копирования, который имеет побочные эффекты или делает что-то кроме простого копирования.

В любом компиляторе процесс анализа синтаксиса [и семантики] выполняется до процесса оптимизации кода.

Код должен быть синтаксически верным, иначе он даже не скомпилируется. Только на более позднем этапе (т. Е. Оптимизации кода) компилятор решает исключить временное, которое он создает.

Так что вам нужна доступная копия c-tor.

Здесь вы можете найти это (с вашим комментарием;)):

[стандарт] также говорит, что временная копия может быть исключена, но семантические ограничения (например, доступность) конструктора копирования все еще должны быть проверены.

РВО и НРВО, дружище. Идеально подходит для копирования.

Это оптимизация компилятором.

При оценке: A a = 10; вместо:

  1. сначала построить временный объект через A(int);

  2. строительство a через конструктор копирования и передачи во временный;

компилятор просто построит a с помощью A(int),

Другие вопросы по тегам