Разница в вызове конструктора по умолчанию между C ++ 14 и C ++ 17 - PullRequest
0 голосов
/ 04 декабря 2018

Рассмотрим следующий код (вопрос следует ниже):

#include <iostream>

struct Type0
{
    Type0(char* c)
    {}
};

struct Type1
{
    Type1(int* i=nullptr) : i_(i)
    {}

    Type1(const Type1& other) = default;

    int* i_;
};

template <typename ...>
struct Composable;

template <typename T0, typename ... T>
struct Composable<T0, T...> : T0, Composable<T...>
{
    Composable()
    {
        std::cout << "Default Invoked: " << sizeof...(T) << std::endl;
    }

    Composable(const Composable& other) = default;

    template<typename Arg, typename ... Args>
    Composable(Arg&& arg, Args&& ... args) :
        T0(std::forward<Arg>(arg)), Composable<T...>(std::forward<Args>(args)...) 
    {
        std::cout << "Non-default invoked: " << sizeof...(T) << std::endl;
    }
};

template <>
struct Composable<>{};

int main()
{
    int i=1;
    char c='c';

    auto comp = Composable<Type0, Type1>(&c, &i);

    std::cout << comp.i_ << std::endl;
}

Вы можете найти живой код здесь .Этот код обладает интересным свойством: в зависимости от того, компилируете ли вы его с опцией --std=C++17 или --std=C++14, поведение меняется (вы можете попробовать это по моей ссылке на живой код: отредактируйте параметр вызова g ++ --std ввнизу слева).

С --std=c++14 вы получите следующий вывод:

Non-default invoked: 0
Non-default invoked: 1
Default Invoked: 0
Non-default invoked: 1
0x0

С --std=C++17 вместо этого вы получите:

Non-default invoked: 0
Non-default invoked: 1
0x7ffcdf02766c

Дляменя эта разница сбивает с толку.Кажется очевидным, что версия C ++ 17 работает правильно, а C ++ 14 - неправильно.Версия C ++ 14 вызывает конструктор по умолчанию как Composable, так и (из него) Type1 (отсюда и последняя строка вывода 0x0, так как Type1 предоставляет это как значение по умолчанию дляего i параметр конструктора).Однако я не вижу места, где конструктор по умолчанию должен вызываться.

Более того, если я вообще закомментирую конструктор по умолчанию Composable, версия C ++ 17 делает точно то же самое, что и раньше, тогда как версия C ++ 14 теперь не компилируется, жалуясь наотсутствие конструктора по умолчанию.Если была какая-либо надежда на то, что разница каким-то образом объясняется различным поведением оптимизации, этот факт наверняка убьет ее (надежда была в любом случае мала, поскольку наблюдаемая разница сохраняется на всех уровнях оптимизации, включая 0).

Кто-нибудь может объяснить эту разницу?Является ли поведение C ++ 14 ошибкой или каким-то предполагаемым поведением, которое я не понимаю?Если поведение C ++ 14 является правильным в правилах C ++ 14, может кто-нибудь объяснить, откуда поступают вызовы конструктора по умолчанию?

1 Ответ

0 голосов
/ 04 декабря 2018

Гарантированное разрешение копирования.

Эта строка:

auto comp = Composable<Type0, Type1>(&c, &i);

В C ++ 17 это означает то же самое, что и:

Composable<Type0, Type1> comp(&c, &i);

И еслиЕсли вы перейдете на эту версию, вы увидите такое же поведение между C ++ 14 и C ++ 17.Однако в C ++ 14 это все еще конструкция перемещения (или, более технически правильная, как вы увидите через минуту, инициализация копирования).Но в Composable у вас нет неявно сгенерированного конструктора перемещения, потому что у вас есть объявленный пользователем конструктор копирования.В результате для конструкции перемещения ваш шаблон конструктора «Вызов по умолчанию» вызывается (это лучше, чем конструктор копирования) в версии C ++ 14:

template<typename Arg, typename ... Args>
Composable(Arg&& arg, Args&& ... args) :
    T0(std::forward<Arg>(arg)), Composable<T...>(std::forward<Args>(args)...) 

Здесь, Arg - это Composable<Type0, Type1>, а Args - пустая пачка.Мы делегируем конструктору T0 (Type0), перенаправляя весь Composable (который работает, потому что он наследуется от Type0 публично, поэтому мы получаем неявно сгенерированный конструктор перемещения там) и Composable<Type1> 's конструктор по умолчанию (потому что args пуст).

Этот шаблон конструктора не является действительно надлежащим конструктором перемещения - он вообще не завершает инициализацию члена Type1,Вместо того, чтобы перемещаться с Type1::i_ с правой стороны, вы вызываете Type1::Type1(), конструктор по умолчанию, поэтому в итоге вы получите 0.

Если вы добавите правильный конструктор перемещения:

Composable(Composable&& other) = default;

Тогда вы снова увидите такое же поведение между C ++ 14 и C ++ 17.

...