оптимизация конструктора копии c ++ [дубликат]

Это даст вам некоторое представление о том, как Java действительно работает до такой степени, что в следующем обсуждении о передаче Java по ссылке или передаче по значению вы просто будете улыбаться: -)

Шаг один, пожалуйста, удалите из вашего разума это слово, которое начинается с «p» «_ _ _ _ _ _ _», особенно если вы исходите из других языков программирования. Java и «p» не могут быть записаны в одной книге, форуме или даже в txt.

Шаг два помнит, что при передаче объекта в метод, по которому вы передаете ссылку Object, а не сам объект

  • Студент : Мастер, означает ли это, что Java является передачей по ссылке?
  • Мастер : Grasshopper, No.

Теперь подумайте о том, что ссылка / переменная объекта делает /:

  1. Переменная содержит биты, которые сообщают JVM, как получить к ссылочному объекту в памяти (куча).
  2. При передаче аргументов методу вы НЕ передаете ссылочную переменную, а копию битов в ссылочной переменной. Что-то вроде этого: 3bad086a. 3bad086a представляет способ доступа к переданному объекту.
  3. Итак, вы просто передаете 3bad086a, что это значение ссылки.
  4. Вы передаете значение ссылки и не самой ссылкой (а не объекта).
  5. Это значение фактически КОПИРОВАНО и дано методу .

В следующем (пожалуйста, не пытайтесь скомпилировать / выполнить это ...):

1. Person person;
2. person = new Person("Tom");
3. changeName(person);
4.
5. //I didn't use Person person below as an argument to be nice
6. static void changeName(Person anotherReferenceToTheSamePersonObject) {
7.     anotherReferenceToTheSamePersonObject.setName("Jerry");
8. }

Что происходит?

  • Переменная person создана в строке # 1, а в начале она равна нулю.
  • В объекте # 2, сохраненном в памяти, создан новый объект Person, а переменной person присваивается ссылка на объект Person. То есть, его адрес. Скажем, 3bad086a.
  • Переменная person , удерживающая адрес объекта, передается функции в строке # 3.
  • В строке # 4 вы можете прослушивать звук молчания
  • Проверить комментарий на строке # 5
  • Локальная переменная метода - anotherReferenceToTheSamePersonObject - создана, а затем идет магическая строка # 6: переменная / ссылка person копируется по битам и передается в anotherReferenceToTheSamePersonObject внутри функции. Никаких новых экземпляров Person не создается. Оба « person » и « anotherReferenceToTheSamePersonObject « сохраняют одно и то же значение 3bad086a. Не пытайтесь это, но человек == anotherReferenceToTheSamePersonObject будет правдой. Обе переменные имеют ИДЕНТИФИКАЦИОННЫЕ КОПИИ ссылки, и оба они относятся к одному объекту Person, SAME Object on the Heap и NOT A COPY.

Изображение стоит тысячи слов:

Pass by Value [/g12]

Обратите внимание, что стрелки anotherReferenceToTheSamePersonObject направлены к объекту а не к переменному человеку!

Если вы этого не поняли, просто доверьтесь мне и помните, что лучше сказать, что Java проходит по значению. Ну, пройдем по эталонному значению. Ну, еще лучше pass-by-copy-of-the-variable-value! ;)

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

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

  • Если это примитивный тип данных, эти биты будут содержать значение самого примитивного типа данных.
  • Если это объект, в котором бит будет содержать значение адреса, сообщающего JVM, как добраться до объекта.

Java - это пропускная способность, потому что внутри метода вы можете измените ссылочный объект столько, сколько хотите, но как бы вы ни старались, вы никогда не сможете изменить переданную переменную, которая будет поддерживать привязку (а не p _ _ _ _ _ _ _) того же объекта независимо от того, что!


Вышеуказанная функция changeName никогда не сможет изменить фактическое содержимое (битовые значения) переданной ссылки. В другом слове changeName не может заставить Person лицо ссылаться на другой Object.


Конечно, вы можете сократить его и просто сказать, что Java является передачей по значению!

279
задан Community 23 May 2017 в 12:03
поделиться

4 ответа

Введение

Технический обзор - пропустить этот ответ .

Для распространенных случаев, когда происходит копирование - пропустить этот ответ .

Копирование elision - это оптимизация, выполняемая большинством компиляторов для предотвращения дополнительных (потенциально дорогостоящих) копий в определенных ситуациях. Он делает возвращение по значению или пропускной способностью практически осуществимым (применяются ограничения).

Это единственная форма оптимизации, которая исключает (ha!) Правило as-if - копирование может применяться даже если копирование / перемещение объекта имеет побочные эффекты.

Следующий пример, взятый из Wikipedia :

struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
};

C f() {
  return C();
}

int main() {
  std::cout << "Hello World!\n";
  C obj = f();
}

В зависимости от компилятора & amp; все допустимые значения:

Hello World! Была сделана копия. Была сделана копия.


Hello World! Копия была сделана.


Hello World!

Это также означает, что можно создавать меньше объектов, t полагаться на определенное количество вызываемых деструкторов. Вы не должны иметь критической логики внутри copy / move-constructors или destructors, так как вы не можете полагаться на их вызываемые.

Если вызов конструктора копирования или перемещения отменяется, этот конструктор должен все же существуют и должны быть доступны. Это гарантирует, что копирование не позволяет копировать объекты, которые обычно не могут быть скопированы, например. потому что у них есть частный или удаленный конструктор copy / move.

C ++ 17: Начиная с C ++ 17, Copy Elision гарантируется, когда объект возвращается напрямую:

struct C {
  C() {}
  C(const C&) { std::cout << "A copy was made.\n"; }
};

C f() {
  return C(); //Definitely performs copy elision
}
C g() {
    C c;
    return c; //Maybe performs copy elision
}

int main() {
  std::cout << "Hello World!\n";
  C obj = f(); //Copy constructor isn't called
}
161
ответ дан Jorge Perez 19 August 2018 в 07:28
поделиться
  • 1
    не могли бы вы объяснить, когда произойдет второй выход, и когда 3-й? – zhangxaochen 19 June 2014 в 15:00
  • 2
    – Luchian Grigore 19 June 2014 в 16:11
  • 3
    @zhangxaochen, 1-й вывод: копия 1 находится от возврата к temp и копирует 2 из temp в obj; 2-е место, когда один из вышеперечисленных вариантов оптимизирован, вероятно, копия reutnr устранена; трое оба лишены – victor 7 November 2014 в 17:06
  • 4
    Хм, но, на мой взгляд, это ДОЛЖНО быть функцией, на которую мы можем положиться. Потому что, если мы не можем, это серьезно повлияет на то, как мы реализуем наши функции в современном C ++ (RVO vs std :: move). Во время просмотра некоторых видеороликов CppCon 2014 у меня действительно сложилось впечатление, что все современные компиляторы всегда делают RVO. Кроме того, я где-то читал, что без каких-либо оптимизаций компиляторы применяют его. Но, конечно, я не уверен в этом. Вот почему я спрашиваю. – j00hi 5 February 2015 в 09:02
  • 5
    @ j00hi: никогда не записывайте move в оператор return - если rvo не применяется, возвращаемое значение по умолчанию все равно выведено. – MikeMB 10 March 2015 в 02:32

Общие формы копирования elision

Для технического обзора - пропустить этот ответ .

Для менее технического вида & amp; введение - пропустить этот ответ .

(Именован) Оптимизация возвращаемого значения является распространенной формой копирования. Это относится к ситуации, когда объект, возвращаемый значением из метода, имеет свою копию. Пример, приведенный в стандарте, иллюстрирует именованную оптимизацию возвращаемого значения, так как объект назван.

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  Thing t;
  return t;
}
Thing t2 = f();

Оптимизация регулярного значения возвращается, когда возвращается время:

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
Thing f() {
  return Thing();
}
Thing t2 = f();

Другие распространенные места, где происходит копирование, - это когда время передается по значению:

class Thing {
public:
  Thing();
  ~Thing();
  Thing(const Thing&);
};
void foo(Thing t);

foo(Thing());

или когда исключение выбрано и поймано по значению:

struct Thing{
  Thing();
  Thing(const Thing&);
};

void foo() {
  Thing c;
  throw c;
}

int main() {
  try {
    foo();
  }
  catch(Thing c) {  
  }             
}

Общие ограничения для копирования:

  • множественные точки возврата
  • условная инициализация

Большинство компиляторов коммерческого уровня поддерживают copy elision & amp; (N) RVO (в зависимости от настроек оптимизации).

73
ответ дан Community 19 August 2018 в 07:28
поделиться
  • 1
    Мне было бы интересно увидеть «Общие ограничения», пункты пули объяснены немного ... что делает эти ограничивающие факторы? – phonetagger 16 January 2013 в 19:54
  • 2
    @phonetagger Я связан с статьей msdn, надеюсь, что очистит некоторые вещи. – Luchian Grigore 16 January 2013 в 21:11

Копирование elision - это метод оптимизации компилятора, который устраняет ненужное копирование / перемещение объектов.

В следующих случаях компилятору разрешено пропускать операции копирования / перемещения и, следовательно, не вызывать связанный конструктор:

  1. NRVO (Именованная оптимизация возвращаемого значения): If функция возвращает тип класса по значению, а выражение оператора return - это имя энергонезависимого объекта с автоматическим временем хранения (который не является параметром функции), а затем копирование / перемещение, которое будет выполняться не оптимизирующим компилятор может быть опущен. Если это так, возвращаемое значение создается непосредственно в хранилище, к которому в противном случае возвращаемое значение функции было бы перемещено или скопировано.
  2. RVO (Оптимизация возвращаемого значения): если функция возвращает неназванный временный объект, который будет перемещен или скопирован в место назначения наивным компилятором, копия или перемещение могут быть опущены в соответствии с 1.
#include <iostream>  
using namespace std;

class ABC  
{  
public:   
    const char *a;  
    ABC()  
     { cout<<"Constructor"<<endl; }  
    ABC(const char *ptr)  
     { cout<<"Constructor"<<endl; }  
    ABC(ABC  &obj)  
     { cout<<"copy constructor"<<endl;}  
    ABC(ABC&& obj)  
    { cout<<"Move constructor"<<endl; }  
    ~ABC()  
    { cout<<"Destructor"<<endl; }  
};

ABC fun123()  
{ ABC obj; return obj; }  

ABC xyz123()  
{  return ABC(); }  

int main()  
{  
    ABC abc;  
    ABC obj1(fun123());//NRVO  
    ABC obj2(xyz123());//NRVO  
    ABC xyz = "Stack Overflow";//RVO  
    return 0;  
}

**Output without -fno-elide-constructors**  
root@ajay-PC:/home/ajay/c++# ./a.out   
Constructor    
Constructor  
Constructor  
Constructor  
Destructor  
Destructor  
Destructor  
Destructor  

**Output with -fno-elide-constructors**  
root@ajay-PC:/home/ajay/c++# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors    
root@ajay-PC:/home/ajay/c++# ./a.out   
Constructor  
Constructor  
Move constructor  
Destructor  
Move constructor  
Destructor  
Constructor  
Move constructor  
Destructor  
Move constructor  
Destructor  
Constructor  
Move constructor  
Destructor  
Destructor  
Destructor  
Destructor  
Destructor  

Даже когда происходит копирование и конструктор copy / move (как будто никакой оптимизации вообще не было), в противном случае программа плохо сформирована.

Вы должны разрешить такое копирование только в тех местах, где это не повлияет наблюдаемое поведение вашего программного обеспечения. Копирование elision - единственная форма оптимизации, которая позволяет иметь (то есть elide) наблюдаемые побочные эффекты. Пример:

#include <iostream>     
int n = 0;    
class ABC     
{  public:  
 ABC(int) {}    
 ABC(const ABC& a) { ++n; } // the copy constructor has a visible side effect    
};                     // it modifies an object with static storage duration    

int main()   
{  
  ABC c1(21); // direct-initialization, calls C::C(42)  
  ABC c2 = ABC(21); // copy-initialization, calls C::C( C(42) )  

  std::cout << n << std::endl; // prints 0 if the copy was elided, 1 otherwise
  return 0;  
}

Output without -fno-elide-constructors  
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp  
root@ajay-PC:/home/ayadav# ./a.out   
0

Output with -fno-elide-constructors  
root@ajay-PC:/home/ayadav# g++ -std=c++11 copy_elision.cpp -fno-elide-constructors  
root@ajay-PC:/home/ayadav# ./a.out   
1

GCC предоставляет возможность -fno-elide-constructors отключить копирование. Если вы хотите избежать возможного копирования, используйте -fno-elide-constructors.

Теперь почти все компиляторы обеспечивают копирование при разрешении оптимизации (и если для его отключения не установлен другой параметр).

Заключение

С каждым экземпляром копии исключается одна конструкция и одно совпадение с уничтожением копии, что позволяет сэкономить время процессора, а один объект не создан, что позволяет сэкономить место в стеке кадр.

34
ответ дан underscore_d 19 August 2018 в 07:28
поделиться
  • 1
    утверждение ABC obj2(xyz123()); - это NRVO или RVO? не получает ли временная переменная / объект такой же, как ABC xyz = "Stack Overflow";//RVO – Asif Mushtaq 28 August 2015 в 02:45
  • 2
    Грамматика и форматирование в этом посте оставляют желать лучшего. Я надеюсь, что кто бы ни просмотрел мои предложенные правки, согласен с тем, что это делает этот взгляд более похожим на сообщение, которое получает 11 upvotes. – underscore_d 27 June 2016 в 19:53
  • 3
    Чтобы получить более конкретную иллюстрацию RVO, вы можете обратиться к сборке, которую генерирует компилятор (изменить флаг компилятора -fno-elide-constructors, чтобы увидеть diff). godbolt.org/g/Y2KcdH – Gab是好人 3 December 2016 в 17:18
80
ответ дан Community 30 October 2018 в 19:40
поделиться
Другие вопросы по тегам:

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