2017-04-06 4 views
1

Je suis toujours nouveau en C++, alors portez-moi. J'essayais d'en savoir plus sur le fonctionnement de std :: move et j'ai vu un exemple où ils utilisaient std :: move pour déplacer la chaîne vers une fonction différente, puis montraient en utilisant std :: cout qu'il ne restait aucune chaîne. Je pensais cool, nous allons voir si je peux faire ma propre classe et faire la même chose:Que fait String ce que je ne fais pas? C++ 11

#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; 
} 

Et voici ma sortie:

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. 

Comme vous pouvez le voir, il se bloque lors de son entrée g la deuxième fois, jamais même à l'opérateur < <() fonction. Comment se fait-il que la chaîne std :: string s vide puisse être transmise à g où mon entier vide écrase le programme?

Édition: Correction d'un nouvel int contre une nouvelle erreur int []. Merci à n.m. Votre "entier vide" bloque le programme car il contient un pointeur nul.

+0

Un constructeur de déplacement roulé à la main utilise typiquement 'std :: move' pour déplacer des membres. – Peter

+0

Merci pour l'aide, qui a résolu le problème. Ce qui si je comprends bien était que dans le constructeur de déplacement (et l'opérateur d'affectation) je traitais i.m_i comme lvalue plutôt que comme valeur. Est-ce correct? – davidbear

Répondre

1

Vous essayez de le déréférencer lorsque vous l'utilisez du côté droit de la tâche.

Une chaîne vide est une chaîne normale utilisable. Il n'y a pas de déréférences de pointeur null non vérifiées dans le code std::string.

Vous devez vous assurer que l'état vide de votre objet est utilisable. Commencez par définir un constructeur par défaut. Cela a-t-il un sens pour votre classe? Sinon, alors déplacer la sémantique ne l'est probablement pas non plus. Si oui, un objet déplacé dans le constructeur de déplacement devrait probablement se retrouver dans le même état qu'un objet construit par défaut. Une affectation de mouvement peut agir comme une opération de swap, de sorte que le côté droit peut finir par être vide ou non.

Si vous ne souhaitez pas définir un état vide utilisable pour votre classe et que vous souhaitez toujours déplacer la sémantique, vous ne pouvez tout simplement pas utiliser un objet après son déplacement. Vous devez toujours vous assurer qu'un objet vide est destructible.

+0

Le but de cet exercice n'était pas pratique, mais d'apprendre comment std :: move a fonctionné. Comment peut-on rendre un objet vide destructible? – davidbear

+1

Le destructeur ne doit pas tomber en panne ou avoir d'autres comportements indésirables. Cela dépend de ce que fait le destructeur, mais fondamentalement, s'il y a un pointeur que vous voulez supprimer, le pointeur doit pointer vers un objet qui peut être 'delete'd, ou être un' nullptr'. Vous l'avez déjà dans votre classe. Attention à un autre bug, parfois vous appelez 'new int' et parfois' new int [1] '. C'est illégal. –

+0

J'ai corrigé le problème "new int/new int [1]" (merci, voir ci-dessus). J'ai déterminé que le programme se bloque avant d'appeler std :: cout, donc l'action d'appeler g avec un entier vide est en quelque sorte différente d'appeler g avec une chaîne std :: string vide. Il me manque toujours quelque chose. Désolé d'être si dense. – davidbear