Оптимизация компилятора неявного преобразования конструктора - PullRequest
2 голосов
/ 10 августа 2011

В следующем коде я ожидаю, что вызывается A конструктор, за которым следует A конструктор копирования.Однако оказывается, что вызывается только конструктор.

// MSVC++ 2008
class A
{
public:
   A(int i):m_i(i)
   {
      cout << "constructor\n";
   }
   A(const A& a)
   {
      m_i = a.m_i;
      cout << "copy constructor\n";
   }

private:
   int m_i;
};

int main()
{
   // only A::A() is called
   A a = 1;
   return 0;
}

Полагаю, компилятор достаточно умен, чтобы оптимизировать второй вызов и инициализировать объект a непосредственно конструктором.Так это стандартное поведение или просто реализация?

Ответы [ 2 ]

6 голосов
/ 10 августа 2011

Стандартно, но оптимизация не предусмотрена.

На самом деле, я считаю, что это связано с оптимизацией, но она все еще полностью стандартна.

Этот код:

A a = 1;

вызывает преобразователь †† из A. A имеет единственный конструктор преобразования A(int i), который позволяет неявное преобразование из int в A.

Если вы добавите объявление конструктора к explicit, вы обнаружите, что код не будет компилироваться.

class A
{
public:
    explicit A(int i) : m_i(i) // Note "explicit"
    {
        cout << "constructor\n";
    }

    A(const A& a)
    {
        m_i = a.m_i;
        cout << "copy constructor\n";
    }

private:
    int m_i;
};

void TakeA(A a)
{
}

int main()
{
    A a = 1;     // Doesn't compile
    A a(1);      // Does compile
    TakeA(1);    // Doesn't compile
    TakeA(A(1)); // Does compile
    return 0;
}

† Снова посмотрев на стандарт, я, возможно, изначально ошибался.

8.5 Инициализаторы [dcl.init]

12. Инициализация, которая происходит при передаче аргумента, функция возврат, выбрасывание исключения (15.1), обработка исключения (15.3), и заключенные в скобки списки инициализаторов (8.5.1) называются копия-инициализация и эквивалентна форме

   T x = a;

14. Семантика инициализаторов следующая. пункт назначения тип - это тип объекта или ссылки, которые инициализируются, и source type - это тип выражения инициализатора. Тип источника не определяется, когда инициализатор заключен в фигурные скобки или когда список выражений в скобках.

...

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

...

Так что в каком-то смысле это очень оптимизация. Но я не стал бы беспокоиться об этом, так как это явно разрешено стандартом, и почти каждый компилятор в настоящее время делает elison.

Более подробное описание инициализации см. в этой статье (# 36) . Статья, кажется, согласна с приведенной выше интерпретацией стандарта:

ПРИМЕЧАНИЕ: в последнем случае ("T t3 = u;") компилятор может вызвать оба определяемое пользователем преобразование (для создания временного объекта) и T-копия конструктор (чтобы построить t3 из временного), или он мог выбрать исключить временный и построить T3 непосредственно от вас (что было бы завершитьэквивалентно "T t3 (u);").С июля 1997 года и в окончательном проекте стандарта широта компилятора для исключения временных объектов была ограничена, но она все еще разрешена для этой оптимизации и для оптимизации возвращаемого значения.

††

ИСО / МЭК 14882: 2003 C ++ Стандартная ссылка

12.3.1 Преобразование с помощью конструктора [class.conv.ctor]

1. Конструктор, объявленный без спецификатора функции explicit, который можно вызывать с помощью одного параметра, указывает преобразование типа его первого параметра в тип его класса.Такой конструктор называется конвертирующим конструктором. [Пример:

     class X {
         // ...
     public:
         X(int);
         X(const char*, int =0);
     };

     void f(X arg)
     {
         X a = 1;        // a = X(1)
         X b = "Jessie"; // b = X("Jessie",0)
         a = 2;          // a = X(2)
         f(3);           // f(X(3))
     }

- конец примера]

2. Явный конструктор создает объекты так же, как не- явные конструкторы, но только в тех случаях, когда синтаксис прямой инициализации (8.5) или где явно используются преобразования (5.2.9, 5.4).Конструктор по умолчанию может быть явным конструктором;такой конструктор будет использоваться для выполнения инициализации по умолчанию или инициализации значения (8.5). [Пример:

     class Z {
     public:
         explicit Z();
         explicit Z(int);
         // ...
     };

     Z a;                      // OK: default-initialization performed
     Z a1 = 1;                 // error: no implicit conversion
     Z a3 = Z(1);              // OK: direct initialization syntax used
     Z a2(1);                  // OK: direct initialization syntax used
     Z* p = new Z(1);          // OK: direct initialization syntax used
     Z a4 = (Z)1;              // OK: explicit cast used
     Z a5 = static_cast<Z>(1); // OK: explicit cast used

- конец примера]

2 голосов
/ 10 августа 2011

Нет оптимизации здесь.Когда = используется в инициализации, это эквивалентно (почти) вызову конструктора с правой частью в качестве аргумента.Итак, это:

A a = 1;

(в основном) эквивалентно этому:

A a(1);
...