Qubc
@Qubc
Ненавижу полисемию.

Почему msvc оптимизирует конструкторы несмотря на флаги?

Почему msvc оптимизирует конструкторы (использует RVO?) несмотря на флаги? Пытаюсь разобраться в the copy-and-swap idiom - в g++ всё вроде логично получается, а тут - теряются конструкторы и приходится догадываться.
#include <algorithm> // std::copy
#include <cstddef> // std::size_t
#include <iostream>
class dumb_array {
private:
static int count;
int id;
    std::size_t mSize;
    int* mArray;
public:
    dumb_array (std::size_t size = 0)
        : mSize (size),
        mArray (mSize ? new int[mSize] () : 0) {
        
        id = count++;
        std::cout << id << ' ' << "dumb_array()" << '\n';
    }

    dumb_array (const dumb_array& other)
        : mSize (other.mSize),
        mArray (mSize ? new int[mSize] : 0) {
        id = count++;
        std::cout << id << ' ' << "dumb_array(dumb_array const &)" << '\n';

        // note that this is non-throwing, because of the data
        // types being used; more attention to detail with regards
        // to exceptions must be given in a more general case, however
        std::copy (other.mArray, other.mArray + mSize, mArray);
    }

    ~dumb_array () {
        delete[] mArray;
    }

   friend void swap (dumb_array& first, dumb_array& second) {
       //swapping of the content instead the objects itsself   
       std::swap (first.mSize, second.mSize);
       std::swap (first.mArray, second.mArray);
   }

    dumb_array & operator=( dumb_array temp) { 
        id = count++;
        std::cout << id << ' ' << " operator=( dumb_array tmp) " << '\n';
       swap (*this, temp);
        return (*this);
    }

    dumb_array (dumb_array&& other) noexcept : dumb_array () { // initialize via default constructor, C++11 only  delegating constructor
        id = count++;
        std::cout << id << ' ' << "dumb_array(dumb_array &&)" << '\n';
       swap (*this, other);// ???
    }
};

int dumb_array::count (0);

int main () {
    dumb_array imnotdumb1;
    imnotdumb1 = dumb_array();

}


// -std=gnu++03 -fno-elide-constructors  -m64 -O0 без &&
// dumb_array()
// dumb_array()
// dumb_array(dumb_array const &)
//  operator=( dumb_array tmp) 

// -std=gnu++11 -fno-elide-constructors  -m64 -O0
// 0 dumb_array()
// 1 dumb_array()
// 2 dumb_array()
// 3 dumb_array(dumb_array &&)
// 4  operator=( dumb_array tmp) 

// msvc /Od С++14
// 0 dumb_array()
// 1 dumb_array()
// 2  operator=( dumb_array tmp)
  • Вопрос задан
  • 120 просмотров
Решения вопроса 1
@code_panik
Разберемся, в каком порядке выводятся сообщения на экран во втором случае gnu++11.
Сначала в main вызываются два конструктора по умолчанию: для imnotdumb1 и временного объекта dumb_array();.
Далее в main вызывается метод dumb_array & operator=( dumb_array temp), в котором temp инициализируется посредством метода dumb_array (dumb_array&& other), который перед выводом на экран сообщения operator=( dumb_array tmp) вызывает ещё один конструктор по умолчанию.
Итого 3 конструктора по умолчанию, один перемещения, один оператор присваивания.

MSVC вызывает первые два конструктора по умолчанию и применяет move elision к инициализации temp.
То есть весь пример сводится к такому коду
#include <iostream>
using namespace std;

struct Foo {
	Foo() { cout << "default ctor\n"; }
	Foo(Foo&& arg) { cout << "move ctor\n"; }
};

void foo(Foo arg) {
	cout << "foo\n";
}

int main () {    
	foo(Foo());
	return 0;
}


В c++ существуют обязательные (mandatory) и необязательные случаи copy/move elision, https://en.cppreference.com/w/cpp/language/copy_elision.
Применение правила из примера
In the initialization of an object, when the source object is a nameless temporary and is of the same class type (ignoring cv-qualification) as the target object.

до c++17 было необязательным к реализации, на усмотрение разработчиков компилятора.
В Microsoft считают такое правило обязательным вне зависимости от флагов компиляции,
Mandatory copy/move elision in Visual Studio

The C++ standard requires copy or move elision when the returned value is initialized as part of the return statement (such as when a function with return type Foo returns return Foo()). The Microsoft Visual C++ compiler always performs copy and move elision for return statements where it is required to do so, regardless of the flags passed to the compiler. This behavior is unchanged.


До c++17 требовалось, чтобы соответствующий copy/move ctor в случае copy elision всё равно был доступен (cppreference, там же):
This is an optimization: even when it takes place and the copy/move (since C++11) constructor is not called, it still must be present and accessible (as if no optimization happened at all), otherwise the program is ill-formed
Ответ написан
Пригласить эксперта
Ответы на вопрос 1
@dima20155
you don't choose c++. It chooses you
У microsoft какая-то муть с RVO (или это я слишком глупый), сам недавно отлавливал баг с RVO. Правда баг был в дебаггере. GDB норм все показывал, а MSVS нет.
Попробуй отключить RVO оптимизацию /Zc:nrvo- и напиши что будет.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы