C 11 rvalue ссылка, вызывающая конструктор копирования тоже

#c #copy-constructor #move-constructor

Вопрос:

Я тестировал некоторые функции C 11 от некоторых некоторых. Я наткнулся на ссылки на значения r и конструкторы перемещения.

Я реализовал свой первый конструктор ходов, вот он:

 #include <iostream>
#include <vector>
using namespace std;

class TestClass{

public:
    TestClass(int s):
        size(s), arr(new int[s]){
    }
    ~TestClass(){
        if (arr)
            delete arr;
    }
    // copy constructor
    TestClass(const TestClassamp; other):
            size(other.size), arr(new int[other.size]){
        std::copy(other.arr, other.arr   other.size, arr);
    }

    // move constructor
    TestClass(TestClassamp;amp; other){
        arr=other.arr;
        size=other.size;

        other.arr=nullptr;
        other.size=0;
    }

private:
    int size;
    int * arr;
};

int main(){
    vector<TestClass> vec;

    clock_t start=clock();
    for(int i=0;i<500000;i  ){
        vec.push_back(TestClass(1000));
    }
    clock_t stop=clock();
    cout<<stop-start<<endl;

    return 0;
}
 

Код работает нормально. В любом случае, поместив std::cout внутрь конструктора копирования, я заметил, что он вызывается! И очень часто.. (переместить конструктор 500000 раз, скопировать конструктор 524287 раз).

Что меня удивило больше всего, так это то, что если я закомментирую конструктор копирования из кода, вся программа будет работать намного быстрее, и на этот раз конструктор перемещения вызывается 1024287 раз.

Есть какие-нибудь зацепки?

Комментарии:

1. Какой компилятор вы используете?

2. coliru.stacked-crooked.com/…

3. @pinoscotto Просто примечание: у вас неопределенное поведение , вы должны использовать delete[] , и вам не нужен if (arr) ... деструктор. Поэтому деструктор должен быть простым ~TestClass(){ delete[] arr; } .

4. @pinoscotto Всегда безопасно звонить delete[] без if: (1) если new при инициализации происходит инициализация, деструктор не будет вызван (2) в противном случае вы либо укажете действительный адрес (3), либо у вас есть a nullptr , и всегда безопасно удалить a nullptr (гарантируется стандартом).

Ответ №1:

Включи noexcept свой конструктор ходов:

 TestClass(TestClassamp;amp; other) noexcept {
 

Уточнение: Я собирался рассказать об этом Пьеру, но, к сожалению, источник cppreference только приблизительно верен.

В C 03

 vector<T>::push_back(T)
 

имеет «гарантию строгого исключения». Это означает, что если push_back возникает исключение, вектор остается в том же состоянии, в котором он был до вызова push_back .

Эта гарантия проблематична, если конструктор перемещения создает исключение.

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

Таким образом, для C 11 было установлено правило:

  1. Если T есть noexcept конструктор перемещения, его можно использовать для перемещения элементов из старого буфера в новый.
  2. В противном случае, если T у вас есть конструктор копирования, он будет использоваться вместо этого.
  3. В противном случае (если нет доступного конструктора копирования), то конструктор перемещения все-таки будет использоваться, однако в этом случае гарантия безопасности строгих исключений больше не предоставляется.

Уточнение: «конструктор копирования» в правиле 2 означает конструктор , принимающий a const Tamp; , а не один из так называемых конструкторов Tamp; копирования. 🙂

Комментарии:

1. можете ли вы подробнее рассказать о своем ответе и о том, что делает noexcept?

2. Все в Visual studio 🙁 кроме слов «ошибка C3646: «за исключением»: неизвестный спецификатор переопределения»

3. @Алон accu.org/index.php/conferences/accu_conference_2013/…

4. @Alon здесь обсуждается соответствующая, хотя и предшествующая ратификации C 11, дискуссия . В основном речь идет о сохранении определенных гарантий исключений в стандартных библиотечных контейнерах.

5.@doctorlove: В Visual Studio это throws() может сработать, хотя это означает что-то немного другое, что может вызвать его собственные проблемы.

Ответ №2:

Используйте noexcept конструктор для перемещения :

 TestClass(TestClassamp;amp; other) noexcept { ... }
 

noexcept без такого постоянного выражения это эквивалентно noexcept(true) .

Компилятор может использовать эту информацию для включения определенных оптимизаций для функций, не вызывающих исключений, а также для включения оператора noexcept, который может проверять во время компиляции, объявлено ли конкретное выражение для создания каких-либо исключений.

Например, контейнеры, такие как std::vector, будут перемещать свои элементы, если конструктор перемещения элементов отсутствует, и копировать в противном случае.

Источник : http://en.cppreference.com/w/cpp/language/noexcept_spec

ПРИМЕЧАНИЕ : Это функция C 11. Определенный компилятор, возможно, еще не реализовал его… (например, Visual Studio 2012)

Ответ №3:

Конструктор копирования вызывается, когда используется вся зарезервированная память внутри std::vector . std::vector::reserve() Перед добавлением элементов необходимо вызвать метод.

 vector<TestClass> vec;
vec.reserve(500000);
 

Ответ №4:

Еще один вопрос. В конструкторе перемещения,

 // move constructor
TestClass(TestClassamp;amp; other){
    arr=other.arr;
    size=other.size;

    other.arr=nullptr;
    other.size=0;
}
 

Разве это не должно быть

arr=std:перемещение(другое.arr);

размер=std:перемещение(другой размер);

потому что

тот факт, что все именованные значения (например, параметры функции) всегда оцениваются как значения lvalue (даже те, которые объявлены как ссылки на значения rvalue)

?