Информация об изменениях

Сообщение Re[7]: Вопрос по value-initialization от 20.05.2017 16:21

Изменено 20.05.2017 16:24 N. I.

Re[7]: Вопрос по value-initialization
Лазар Бешкенадзе:

NI>>И что дальше с этим B() можно было делать (не меняя ничего в классе B)? Попытка передать его куда-нибудь по ссылке могла бы привести к undefined behavior, т.к. реализация была вправе делать копию объекта при инициализации ссылки на const rvalue-выражением, а копирование неинициализированного скалярного объекта неявно сгенерированным копирующим конструктором A влечёт неопределённое поведение. Попытка снять const с копии и присвоить i какое-нибудь значение тоже бы повлекла неопределённое поведение.


ЛБ>Я не понял о чем ты говоришь. Я говорил о том что было до 2003.


Я про C++98 говорил.

ЛБ>Я всегда писал B b = new B(). Просто потому что это единообразно с остальными конструкторами.


Единообразие — это не про C++. Стандарт C++ всегда описывал кучу специальных случаев то на одно, то на другое. Например, new T() ведёт себя по-разному в зависимости от того, является T типом-массивом или нет. Если надо, чтоб new-выражение делало default-initialization, не следует туда сувать эти круглые скобочки.

ЛБ>Если ты имеешь ввиду привязку временных объектов к const ссылкам


Именно её.

NI>>Стоит помнить, что инициализация нулями — это не более, чем часть формального описания observable behavior


ЛБ>Какое observable behavior?


Такое, какое описано в разделе [intro.execution] стандарта. Обнуление происходит на некоей воображаемой абстрактной машине, что позволяет нам предсказать результат работы программы (а точнее, её observable behavior). От реальной реализации не требуется чего-то там обнулять или даже создавать в памяти аналог объекта, который был бы создан на абстрактной машине. От реализации требуется лишь получение результата, который мог бы получиться на абстрактной машине описанным стандартом путём. Некоторые оптимизации, меняющие observable behavior, описаны явным образом. Например, правила C++14 позволяют убирать вызовы replaceable функций operator new и operator delete из new-выражений и delete-выражений, даже если эти функции переопределены пользователем и делают что-то такое, что влияет на observable behavior (например, пишут в файл). Вот такую программу

#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <new>

void *operator new(std::size_t size)
{
    if (void *p = std::malloc(size))
    {
        std::printf("allocation size: %zu\n", size);
        return p;
    }
    throw std::bad_alloc();
}

void operator delete(void *p) noexcept
{
    std::printf("deallocation\n");
    std::free(p);
}

void operator delete(void *p, [[maybe_unused]] std::size_t size) noexcept
{
    operator delete(p);
}

struct A
{
    int i;
    A() { } // user-provided default ctor, does not initialize i
};

struct B { A a; }; // implicitly-defined default ctor

int main()
{
    try
    {
        B *b = new B();
        b->a.i = 10;
        std::printf("b->a.i: %d\n", b->a.i);
        delete b;
    }
    catch (std::bad_alloc &)
    {
        std::printf("out of memory");
    }
}

допустимо соптимизировать до эквивалента

#include <cstdio>

int main()
{
    std::printf("b->a.i: %d\n", 10);
}

ЛБ>Я считаю что единственное полезное что появилось в C++11 это variadic templates. Все остальное, включая auto типы, ссылки на rvalue, move конструкторы, лямбды и инициализация мне нах не нужны. А C++14 и дальше меня просто не интересует.

Ну, не у всех такие скромные запросы.
Re[7]: Вопрос по value-initialization
Лазар Бешкенадзе:

NI>>И что дальше с этим B() можно было делать (не меняя ничего в классе B)? Попытка передать его куда-нибудь по ссылке могла бы привести к undefined behavior, т.к. реализация была вправе делать копию объекта при инициализации ссылки на const rvalue-выражением, а копирование неинициализированного скалярного объекта неявно сгенерированным копирующим конструктором A влечёт неопределённое поведение. Попытка снять const с копии и присвоить i какое-нибудь значение тоже бы повлекла неопределённое поведение.


ЛБ>Я не понял о чем ты говоришь. Я говорил о том что было до 2003.


Я про C++98 говорил.

ЛБ>Я всегда писал B b = new B(). Просто потому что это единообразно с остальными конструкторами.


Единообразие — это не про C++. Стандарт C++ всегда описывал кучу специальных случаев то на одно, то на другое. Например, new T() ведёт себя по-разному в зависимости от того, является T типом-массивом или нет. Если надо, чтоб new-выражение делало default-initialization, не следует туда сувать эти круглые скобочки.

ЛБ>Если ты имеешь ввиду привязку временных объектов к const ссылкам


Именно её.

NI>>Стоит помнить, что инициализация нулями — это не более, чем часть формального описания observable behavior


ЛБ>Какое observable behavior?


Такое, какое описано в разделе [intro.execution] стандарта. Обнуление происходит на некоей воображаемой абстрактной машине, что позволяет нам предсказать результат работы программы (а точнее, её observable behavior). От реальной реализации не требуется чего-то там обнулять или даже создавать в памяти аналог объекта, который был бы создан на абстрактной машине. От реализации требуется лишь получение результата, который мог бы получиться на абстрактной машине описанным стандартом путём. Некоторые оптимизации, меняющие observable behavior, описаны явным образом. Например, правила C++14 позволяют убирать вызовы replaceable функций operator new и operator delete из new-выражений и delete-выражений, даже если эти функции переопределены пользователем и делают что-то такое, что влияет на observable behavior (например, пишут в файл). Вот такую программу

#include <cstddef>
#include <cstdio>
#include <cstdlib>
#include <new>

void *operator new(std::size_t size)
{
    if (void *p = std::malloc(size))
    {
        std::printf("allocation size: %zu\n", size);
        return p;
    }
    throw std::bad_alloc();
}

void operator delete(void *p) noexcept
{
    std::printf("deallocation\n");
    std::free(p);
}

void operator delete(void *p, [[maybe_unused]] std::size_t size) noexcept
{
    operator delete(p);
}

struct A
{
    int i;
    A() { } // user-provided default ctor, does not initialize i
};

struct B { A a; }; // implicitly-defined default ctor

int main()
{
    try
    {
        B *b = new B();
        b->a.i = 10;
        std::printf("b->a.i: %d\n", b->a.i);
        delete b;
    }
    catch (std::bad_alloc &)
    {
        std::printf("out of memory");
    }
}

допустимо соптимизировать до эквивалента следующего кода

#include <cstdio>

int main()
{
    std::printf("b->a.i: %d\n", 10);
}

ЛБ>Я считаю что единственное полезное что появилось в C++11 это variadic templates. Все остальное, включая auto типы, ссылки на rvalue, move конструкторы, лямбды и инициализация мне нах не нужны. А C++14 и дальше меня просто не интересует.

Ну, не у всех такие скромные запросы.