Локальный вызов деструктора С++ в Visual Studio 2008 (отсутствует в GCC)

Возьмите следующую (надуманную) иерархию классов, которая выводит на консоль из конструкторов и деструкторов:

#include <iostream>

class A { 
public:
  A() { std::cout << "A"; }
  ~A() { std::cout << "~A"; } 
};

class B : public A { 
public:   
  B() { std::cout << "B"; }
  ~B() { std::cout << "~B"; } 
};

void func(A a) { }

int main() {   
  B b;
  func(b);
  std::cout << "X";
  return 0; 
}

Скомпилированный под linux с gcc, он печатает AB~AX~B~A, как ожидалось (~A, напечатанный до X, является результатом значения pass-by-value до func, который создает копию, которая разрушается, когда функция возвращается).

Но скомпилированный под окнами VS2008, он печатает AB~A~AX~B~A - откуда берется дополнительная ~A? Он исчезает, если явно определена копия xtor A(A& that) {}; или если деструктор объявлен виртуальным (как и должно быть, возможно).

Ответ 1

Комментарии предполагают, что MSVC 2008 использует временное, которое g++ не делает, чтобы передать параметр. Если это так, это ошибка. Из С++ 03 [dcl.init]/12:

Инициализация, возникающая при передаче аргумента, возврату функции, исключении исключения (15.1), обработке исключения (15.3) и списках инициализаторов, заключенных в фигурные скобки (8.5.1), называется копией-инициализацией и эквивалентна форме T x = a;

Теперь вот критический бит. В T x = a;, если a не является T или не получен из T, то это эквивалентно T x = T(a);, а концептуально используется дополнительное временное. (Это временное право на копирование).

Однако, если a является T или получен из T, тогда не должно быть дополнительного временного. Это то же самое, что T x(a);.

В этом вопросительном коде, поскольку B происходит от a, не должно быть временного.

Поддерживающий текст в С++ 03 находится под [dcl.init]/14 (я выделил части, относящиеся к этому образцу кода вопроса):

Если тип назначения является классом класса (возможно, cv-qualit):

  • Если класс является агрегатом (8.5.1), а инициализатор представляет собой список, заключенный в скобки, см. 8.5.1.
  • Если инициализация является прямой инициализацией или если она является копией-инициализацией, где cv-неквалифицированная версия типа источника является тем же классом, что и или производным классом, класс назначения, конструкторы. Соответствующие конструкторы перечислены (13.3.1.3), а лучший выбирается с помощью разрешения перегрузки (13.3). Выбранный таким образом конструктор вызывается для инициализации объекта с выражением инициализатора (s) в качестве его аргумента (ов). Если конструктор не применяется или разрешение перегрузки неоднозначно, инициализация плохо сформирована.
  • В противном случае (т.е. для остальных случаев инициализации копии) пользовательские последовательности преобразований, которые могут конвертировать из типа источника в тип назначения или (когда используется функция преобразования) с их производный класс перечислены, как описано в 13.3.1.4, и лучший из них выбирается посредством разрешения перегрузки (13.3). Если преобразование не может быть выполнено или неоднозначно, инициализация плохо сформирована. Выбранная функция вызывается с выражением инициализатора в качестве аргумента; если функция является конструктором, , вызов инициализирует временный для целевого типа. Результат вызова (который является временным для случая конструктора) затем используется для прямого инициализации, согласно вышеприведенным правилам, объекта, который является местом назначения инициализации копирования. В некоторых случаях допускается исключить копирование, присущее этой прямой инициализации, путем создания промежуточного результата непосредственно в инициализированном объекте; см. 12.2, 12.8.