Что делает String, чего я не делаю? С++ 11

Я все еще новичок в С++, так что терпите меня.

Я пытался узнать больше о том, как работает std::move, и увидел пример, в котором они использовали std::move для перемещения строки в другую функцию, а затем с помощью std::cout показали, что строки не осталось. Я подумал, круто, давайте посмотрим, смогу ли я создать свой собственный класс и сделать то же самое:

#include <iostream>
#include <string>

class integer
{
private:
    int *m_i;
public:
    integer(int i=0) : m_i(new int{i})
    {
        std::cout << "Calling Constructor\n";
    }

    ~integer()
    {
        if(m_i != nullptr) {
            std::cout << "Deleting integer\n";
            delete m_i;
            m_i = nullptr;
        }
    }

    integer(integer&& i) : m_i(nullptr)  // move constructor
    {
        std::cout << "Move Constructor\n";
        m_i = i.m_i;
        i.m_i = nullptr;
    }
    integer(const integer& i) : m_i(new int) {  // copy constructor
        std::cout << "Copy Constructor\n";
        *m_i = *(i.m_i);
    }
//*
    integer& operator=(integer&& i) {   // move assignment
        std::cout << "Move Assignment\n";
        if(&i != this) {
            delete m_i;
            m_i = i.m_i;
            i.m_i = nullptr;
        }
        return *this;
    }
    integer& operator=(const integer &i) {   // copy assignment
        std::cout << "Copy Assignment\n";
        if(&i != this) {
           m_i = new int;
           *m_i = *(i.m_i);
        }
       return *this;
    }
    int& operator*() const { return *m_i; }
    int* operator->() const { return m_i; }

    bool empty() const noexcept {
       if(m_i == nullptr) return true;
       return false;
    }

    friend std::ostream& operator<<(std::ostream &out, const integer i) {
       if(i.empty()) {
           std::cout << "During overload, i is empty\n";
           return out;
       }
    out << *(i.m_i);
    return out;
    }
};

void g(integer i) { std::cout << "G-wiz - "; std::cout << "The g value is " <<  i << '\n'; }
void g(std::string s) { std::cout << "The g value is " << s << '\n'; }

int main()
{
    std::string s("Hello");

    std::cout << "Now for string\n";
    g(std::move(s));
    if(s.empty()) std::cout << "s is empty\n";
    g(s);
    std::cout << "\nNow for integer\n";
    integer i = 77;
    if(!i.empty()) std::cout << "i is " << i << '\n';
    else std::cout << "i is empty\n";
    g(i);
    std::cout << "Move it\n";
    g(std::move(i));  // rvalue ref called
    if(!i.empty()) std::cout << "i is " << i << '\n';
    else std::cout << "i is empty\n";
    g(i);

    return 0;
}

И это мой вывод:

Now for string
The g value is Hello
s is empty
The g value is

Now for integer
Calling Constructor
Copy Constructor
i is 77
Deleting integer
Copy Constructor
G-wiz - Copy Constructor
The g value is 77
Deleting integer
Deleting integer
Move it
Move Constructor
G-wiz - Copy Constructor
The g value is 77
Deleting integer
Deleting integer
i is empty
Copy Constructor

Process returned 255 (0xFF)   execution time : 7.633 s
Press any key to continue.

Как видите, он падает, когда входит в g ​​во второй раз, даже не дойдя до функции operator‹‹(). Как получилось, что пустой std::string s может быть передан g, где мое пустое целое число i приводит к сбою программы?

Изменить: исправлена ​​ошибка new int vs. new int[]. Спасибо, н.м.


person davidbear    schedule 06.04.2017    source источник
comment
Свернутый вручную конструктор перемещения обычно использует std::move для перемещения элементов.   -  person Peter    schedule 06.04.2017
comment
Спасибо за помощь, это решило проблему. Что, если я понимаю, заключалось в том, что в конструкторе перемещения (и операторе присваивания) я рассматривал i.m_i как lvalue, а не как rvalue. Это правильно?   -  person davidbear    schedule 06.04.2017


Ответы (1)


Ваше «пустое целое» приводит к сбою программы, поскольку оно содержит нулевой указатель. Вы пытаетесь разыменовать его, когда используете его в правой части задания.

Пустая строка — это обычная используемая строка. В коде std::string нет непроверенных разыменований нулевого указателя.

Вы должны убедиться, что пустое состояние вашего объекта пригодно для использования. Начните с определения конструктора по умолчанию. Имеет ли это смысл для вашего класса? Если нет, то семантика перемещения, вероятно, тоже не работает. Если да, перемещенный объект в конструкторе перемещения, вероятно, должен оказаться в том же состоянии, что и объект, сконструированный по умолчанию. Присвоение перемещения может действовать как операция обмена, поэтому правая часть может оказаться либо пустой, либо нет.

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

person n. 1.8e9-where's-my-share m.    schedule 06.04.2017
comment
Смысл этого упражнения был не в практическом, а в том, чтобы узнать, как работает std::move. Как сделать пустой объект разрушаемым? - person davidbear; 06.04.2017
comment
Деструктор не должен аварийно завершать работу или иметь другое нежелательное поведение. Это зависит от того, что делает деструктор, но в основном, если есть указатель, который вы хотите delete, указатель должен указывать на объект, который может быть deleted или быть nullptr. У вас уже есть это в вашем классе. Обратите внимание на другую ошибку: иногда вы вызываете new int, а иногда new int[1]. Это незаконно. - person n. 1.8e9-where's-my-share m.; 06.04.2017
comment
Я исправил проблему с новым int/new int[1] (спасибо, см. выше). Я определил, что программа аварийно завершает работу до вызова std::cout, поэтому вызов g с пустым целым числом несколько отличается от вызова g с пустым std::string. Я все еще что-то упускаю. Извините, что так тупо. - person davidbear; 06.04.2017
comment
Я уже объяснил это. Ваше пустое целое число имеет нулевой указатель. Ваши функции доступа разыменовывают указатель без проверки. Это не разрешено в С++. Вам нужно решить, является ли пустое целое число допустимым для вас объектом. Неясно, какую семантику будет иметь пустое целое число. Сколько будет пустое целое число плюс 2? Что значит напечатать пустое целое число? Нужно все обдумать, записать и реализовать. Пустая строка действительна и имеет четко определенную сеантику. - person n. 1.8e9-where's-my-share m.; 06.04.2017