LINUX.ORG.RU

Это заслуга умного компилятора?

 , move semantics


0

1

Топик навеян ссылкой, которую дал Pinkbyte в нуботреде. В комментариях к статье сказано, что компилятор будет создавать временный объект и вызывать конструктор копирования для версии оператора присваивания приведенной ниже.

Тут оптимальнее переложить создание копии на компилятор, раз уж он всё равно позволяет это делать автоматически (получаем аргумент по значению, а не по ссылке):

Две других реализации этого же оператора присваивания для подопытного демо-класса для наглядности закомментированы.

#include <iostream>

using namespace std;

class Intvec

{
public:
	explicit Intvec(size_t num = 0)
		: m_size(num), m_data(new int[m_size])
	{
		log("constructor");
	}

	~Intvec()
	{
		log("destructor");
		if (m_data) {
			delete[] m_data;
			m_data = 0;
		}
	}

	Intvec(const Intvec& other)
		: m_size(other.m_size), m_data(new int[m_size])
	{
		log("copy constructor");
		for (size_t i = 0; i < m_size; ++i)
			m_data[i] = other.m_data[i];
	}

	Intvec& operator=(Intvec other)
	{
		log("copy assignment operator");
		swap(m_size, other.m_size);
		swap(m_data, other.m_data);
		return *this;
	}

	/*Intvec& operator=(const Intvec& other)
  {
	  log("copy assignment operator");
	  Intvec tmp(other);
	  std::swap(m_size, tmp.m_size);
	  std::swap(m_data, tmp.m_data);
	  return *this;
  } */

  /* Intvec& operator=(Intvec&& other) // Now we are smart enough to use move semantics
{
	log("move assignment operator");
	std::swap(m_size, other.m_size);
	std::swap(m_data, other.m_data);
	return *this;
} */

private:
	void log(const char* msg)
	{
		std::cout << "[" << this << "] " << msg << "\n";
	}

	size_t m_size;
	int* m_data;
};

int main(int argc, char* argv[]) {
	//Intvec v1(42);
	Intvec v2;
	cout << "assigning rvalue..." << endl;
	v2 = Intvec(21);
	//v2 = v1;
	cout << "ended assigning rvalue..." << endl;
	return 0;
}

Как видим на практике этого не происходит — конструктор копирования не вызывается. У меня только один вопрос: Это уже copy elision от умного компилятора или я не допер?

Ведь тогда правка комментатора лишает пример статьи всякого смысла и наглядности, не? ))

★★★★★

Последнее исправление: Twissel (всего исправлений: 2)

А вроде дошло. Видимо, здесь временный объект создаётся «за кулисами». Он как тот суслик :-)

Но наглядность все равно теряется. Поправьте если что.

Twissel ★★★★★
() автор топика
Ответ на: комментарий от Twissel

Как видим на практике

Вывод, наверное, стоило привести.

Это уже copy elision от умного компилятора

Не в этом контексте.

Ведь тогда правка комментатора лишает пример статьи всякого смысла и наглядности, не? ))

Похоже на то.

Видимо, здесь временный объект создаётся «за кулисами».

Не знаю, что имелось в виду, но мне кажется, что эта форма

v2 = Intvec(21);

будучи синтаксическим сахаром для

v2.operator=(Intvec(21));

просто не требует создания временного объекта кроме параметра. Т.е. здесь просто задаётся каким образом создать параметр, а не временный объект.

P.S. В ассемблере временный объект передался загрузкой адреса в регистр, так себе передача по значению :)

xaizek ★★★★★
()

в каком месте вашего кода должен вызваться конструктор копирования ?
по вашему коду только оператор присваивания

anonymous
()

До C++17 тут разрешено (и происходит) copy elision. Например, из драфта 14-го стандарта:

https://timsong-cpp.github.io/cppwp/n4140/class.copy#constructor,copy,elision

… This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies): … when a temporary class object that has not been bound to a reference ([class.temporary]) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move

В C++17 тут вообще не должно быть копирования.

https://timsong-cpp.github.io/cppwp/n4659/dcl.init#17.6

If the destination type is a (possibly cv-qualified) class type: If the initializer expression is a prvalue and the cv-unqualified version of the source type is the same class as the class of the destination, the initializer expression is used to initialize the destination object. [ Example: T x = T(T(T())); calls the T default constructor to initialize x.  — end example ]

anonymous
()

И ещё. Можешь передать опцию -fno-elide-constructors и посмотреть что будет для разных стандартов (

-std=c++14

и -std=c++17)

anonymous
()
Ответ на: комментарий от xaizek

Вывод, наверное, стоило привести.

-std=c++14

[0x7ffefc4e9350] constructor
assigning rvalue...
[0x7ffefc4e9360] constructor
[0x7ffefc4e9350] copy assignment operator
[0x7ffefc4e9360] destructor
ended assigning rvalue...
[0x7ffefc4e9350] destructor

[b]-std=c++14 -fno-elide-constructors[/b]

[0x7ffcd539a650] constructor assigning rvalue... [0x7ffcd539a670] constructor [0x7ffcd539a660] copy constructor [0x7ffcd539a650] copy assignment operator [0x7ffcd539a660] destructor [0x7ffcd539a670] destructor ended assigning rvalue... [0x7ffcd539a650] destructor

-std=c++17 -fno-elide-constructors

[0x7ffc59cc9d60] constructor
assigning rvalue...
[0x7ffc59cc9d80] constructor
[0x7ffc59cc9d70] copy constructor
[0x7ffc59cc9d60] copy assignment operator
[0x7ffc59cc9d70] destructor
[0x7ffc59cc9d80] destructor
ended assigning rvalue...
[0x7ffc59cc9d60] destructor

-std=c++17

[0x7ffe394d8910] constructor
assigning rvalue...
[0x7ffe394d8920] constructor
[0x7ffe394d8910] copy assignment operator
[0x7ffe394d8920] destructor
ended assigning rvalue...
[0x7ffe394d8910] destructor

Вроде я угадал :-)

Т.е. здесь просто задаётся каким образом создать параметр, а не временный объект.

Это я уже цепляюсь к словам (по сабжу все понятно), но все же: какой тогда тип имеет наш параметр, который на самом деле запихнули в регистр?

Twissel ★★★★★
() автор топика
Ответ на: комментарий от Twissel

-std=c++17 -fno-elide-constructors
[0x7ffc59cc9d70] copy constructor

Это баг компилятора. Скорее всего, версия не самая новая.

anonymous
()
Ответ на: комментарий от anonymous

Да, я специально взял gcc 6.3.0 , чтобы посмотреть на поведение компилятора ;-)

Twissel ★★★★★
() автор топика
Ответ на: комментарий от Twissel

Поправлю вывод

-std=c++14 -fno-elide-constructors

[0x7ffcd539a650] constructor 
assigning rvalue... 
[0x7ffcd539a670] constructor 
[0x7ffcd539a660] copy constructor 
[0x7ffcd539a650] copy assignment operator 
[0x7ffcd539a660] destructor 
[0x7ffcd539a670] destructor 
ended assigning rvalue... 
[0x7ffcd539a650] destructor 
Twissel ★★★★★
() автор топика
Последнее исправление: Twissel (всего исправлений: 1)
Ответ на: комментарий от xaizek

P.S. В ассемблере временный объект передался загрузкой адреса в регистр, так себе передача по значению :)

Какой компилятор, какой ключ стандарта?

Twissel ★★★★★
() автор топика
Ответ на: комментарий от xaizek

В ассемблере временный объект передался загрузкой адреса в регистр, так себе передача по значению

А ты чего ожидал? У объекта нетривиальный конструктор копирования. Его дата мемберы через регистры тащить нельзя.

anonymous
()
Ответ на: комментарий от anonymous

в 17 стандарте его там нет, даже с но елиде

anonymous
()
Ответ на: комментарий от Twissel

Вроде я угадал :-)

Да, аноним всё разъяснил. Я смотрел на C++17 где убрали пункт из [class.copy.elision] и добавили более сильную альтернативу в другом месте.

какой тогда тип имеет наш параметр, который на самом деле запихнули в регистр?

Всё тот же Intvec, то просто деталь реализации.

Какой компилятор, какой ключ стандарта?

gcc 5.5.0, стандарт по умолчанию (-std=gnu++98).

xaizek ★★★★★
()
Ответ на: комментарий от anonymous

Это был комментарий о моём незнании x86-64 ABI, а чего-то думал, что он передаст значение без использования регистра (как когда регистров не хватает), но у него нету механизма сигнализировать об этом.

xaizek ★★★★★
()
Ответ на: комментарий от xaizek

Если уже доводить до абсурда, то гулять, так гулять

А с этим примером можно обманным путём (ключами оптимизации, заклятьями Вуду) заставить компилятор выкинуть объект tmp ?

Или он все же строгий малый?

Intvec& operator=(const Intvec& other)
  {
	  log("copy assignment operator");
	  Intvec tmp(other);
	  std::swap(m_size, tmp.m_size);
	  std::swap(m_data, tmp.m_data);
	  return *this;
  }
Twissel ★★★★★
() автор топика
Ответ на: комментарий от Twissel

Аноним выше давал ссылку на контексты, где copy elision может происходить: https://timsong-cpp.github.io/cppwp/n4140/class.copy#constructor,copy,elision

Этого случая там нету и я не думаю, что он где-то отдельно описан. Так что скорее всего нельзя (оптимизировать последовательность вызовов спец. функций это одно, а произвольное взаимодействие с объектов это уже другого порядка сложности). Хотя после инлайнинга, может от того временного объекта и ничего уже не останется (но побочные эффекты конструктора выкинуты не будут).

xaizek ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.