web-dev-qa-db-fra.com

Pourquoi pas d'affectation de déplacement / constructeur de déplacement par défaut?

Je suis un simple programmeur. Les variables de mes membres de classe se composent le plus souvent de types POD et de conteneurs STL. Pour cette raison, je dois rarement écrire des opérateurs d'affectation ou des constructeurs de copie, car ceux-ci sont implémentés par défaut.

Ajoutez à cela, si j'utilise std::move Sur des objets non mobiles, il utilise l'opérateur d'affectation, ce qui signifie que std::move Est parfaitement sûr.

Comme je suis un simple programmeur, j'aimerais profiter des capacités de déplacement sans ajouter de constructeur de déplacement/opérateur d'affectation à chaque classe que j'écris, car le compilateur pourrait simplement les implémenter en tant que "this->member1_ = std::move(other.member1_);... "

Mais ce n'est pas le cas (du moins pas dans Visual 2010), y a-t-il une raison particulière à cela?

Plus important; existe-t-il un moyen de contourner cela?

Mise à jour: Si vous regardez la réponse de GManNickG, il fournit une excellente macro pour cela. Et si vous ne le saviez pas, si vous implémentez la sémantique de déplacement, vous pouvez supprimer la fonction de membre d'échange.

87
Viktor Sehr

La génération implicite de constructeurs de mouvements et d'opérateurs d'affectation a été controversée et il y a eu des révisions majeures dans les versions récentes de la norme C++, donc les compilateurs actuellement disponibles se comporteront probablement différemment en ce qui concerne la génération implicite.

Pour plus d'informations sur l'historique du problème, consultez la liste des documents du WG21 201 et recherchez "mov"

La spécification actuelle (N3225, à partir de novembre) stipule (N3225 12.8/8):

Si la définition d'une classe X ne déclare pas explicitement un constructeur de déplacement, un sera implicitement déclaré comme défaut si et seulement si

  • X n'a pas de constructeur de copie déclaré par l'utilisateur, et

  • X n'a pas d'opérateur d'affectation de copie déclaré par l'utilisateur,

  • X n'a pas d'opérateur d'affectation de déplacement déclaré par l'utilisateur,

  • X n'a pas de destructeur déclaré par l'utilisateur, et

  • le constructeur de déplacement ne serait pas implicitement défini comme supprimé.

Un langage similaire en 12.8/22 spécifie quand l'opérateur d'affectation de déplacement est implicitement déclaré comme par défaut. Vous pouvez trouver la liste complète des modifications apportées pour prendre en charge la spécification actuelle de la génération de mouvements implicites dans N3203: resserrement des conditions de génération de mouvements implicites , qui était largement basée sur l'une des résolutions proposées par l'article de Bjarne Stroustrup. N3201: Se déplacer à droite .

74
James McNellis

Les constructeurs de mouvements générés implicitement ont été pris en compte pour la norme, mais peuvent être dangereux. Voir Dave Abrahams analyse .

En fin de compte, cependant, la norme incluait la génération implicite de constructeurs de déplacement et d'opérateurs d'affectation de déplacement, mais avec une liste assez importante de limitations:

Si la définition d'une classe X ne déclare pas explicitement un constructeur de déplacement, un sera implicitement déclaré comme défaut si et seulement si
- X n'a ​​pas de constructeur de copie déclaré par l'utilisateur,
- X n'a ​​pas d'opérateur d'affectation de copie déclaré par l'utilisateur,
- X n'a ​​pas d'opérateur d'affectation de déplacement déclaré par l'utilisateur,
- X n'a ​​pas de destructeur déclaré par l'utilisateur, et
- le constructeur de déplacement ne serait pas implicitement défini comme supprimé.

Ce n'est pas tout à fait tout ce qu'il y a dans l'histoire. Un ctor peut être déclaré, mais toujours défini comme supprimé:

Un constructeur de copie/déplacement déclaré implicitement est un membre public en ligne de sa classe. Un constructeur de copie/déplacement par défaut pour une classe X est défini comme supprimé (8.4.3) si X a:

- un membre variant avec un constructeur correspondant non trivial et X est une classe de type union,
- un membre de données non statique de type classe M (ou un tableau de celui-ci) qui ne peut pas être copié/déplacé car la résolution de surcharge (13.3), telle qu'appliquée au constructeur correspondant de M, entraîne une ambiguïté ou une fonction qui est supprimé ou inaccessible du constructeur par défaut,
- une classe de base B directe ou virtuelle qui ne peut pas être copiée/déplacée car la résolution de surcharge (13.3), telle qu’appliquée au constructeur correspondant de B, entraîne une ambiguïté ou une fonction supprimée ou inaccessible du constructeur par défaut,
- toute classe de base directe ou virtuelle ou membre de données non statique d'un type avec un destructeur qui est supprimé ou inaccessible du constructeur par défaut,
- pour le constructeur de copie, un membre de données non statique de type référence rvalue, ou
- pour le constructeur de déplacement, un membre de données non statique ou une classe de base directe ou virtuelle avec un type qui n'a pas de constructeur de déplacement et qui n'est pas trivialement copiable.

12
Jerry Coffin

(pour l'instant, je travaille sur une macro stupide ...)

Ouais, j'ai emprunté cette voie aussi. Voici votre macro:

// detail/move_default.hpp
#ifndef UTILITY_DETAIL_MOVE_DEFAULT_HPP
#define UTILITY_DETAIL_MOVE_DEFAULT_HPP

#include <boost/preprocessor.hpp>

#define UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE(pR, pData, pBase) pBase(std::move(pOther))
#define UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE(pR, pData, pBase) pBase::operator=(std::move(pOther));

#define UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR(pR, pData, pMember) pMember(std::move(pOther.pMember))
#define UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT(pR, pData, pMember) pMember = std::move(pOther.pMember);

#define UTILITY_MOVE_DEFAULT_DETAIL(pT, pBases, pMembers)                                               \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE, BOOST_PP_EMPTY, pBases))                      \
        ,                                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR, BOOST_PP_EMPTY, pMembers))                         \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE, BOOST_PP_EMPTY, pBases)  \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT, BOOST_PP_EMPTY, pMembers)     \
                                                                                                        \
            return *this;                                                                               \
        }

#define UTILITY_MOVE_DEFAULT_BASES_DETAIL(pT, pBases)                                                   \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR_BASE, BOOST_PP_EMPTY, pBases))                      \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT_BASE, BOOST_PP_EMPTY, pBases)  \
                                                                                                        \
            return *this;                                                                               \
        }

#define UTILITY_MOVE_DEFAULT_MEMBERS_DETAIL(pT, pMembers)                                               \
        pT(pT&& pOther) :                                                                               \
        BOOST_PP_SEQ_ENUM(BOOST_PP_SEQ_TRANSFORM(                                                       \
            UTILITY_MOVE_DEFAULT_DETAIL_CONSTRUCTOR, BOOST_PP_EMPTY, pMembers))                         \
        {}                                                                                              \
                                                                                                        \
        pT& operator=(pT&& pOther)                                                                      \
        {                                                                                               \
            BOOST_PP_SEQ_FOR_EACH(UTILITY_MOVE_DEFAULT_DETAIL_ASSIGNMENT, BOOST_PP_EMPTY, pMembers)     \
                                                                                                        \
            return *this;                                                                               \
        }

#endif

La

// move_default.hpp
#ifndef UTILITY_MOVE_DEFAULT_HPP
#define UTILITY_MOVE_DEFAULT_HPP

#include "utility/detail/move_default.hpp"

// move bases and members
#define UTILITY_MOVE_DEFAULT(pT, pBases, pMembers) UTILITY_MOVE_DEFAULT_DETAIL(pT, pBases, pMembers)

// base only version
#define UTILITY_MOVE_DEFAULT_BASES(pT, pBases) UTILITY_MOVE_DEFAULT_BASES_DETAIL(pT, pBases)

// member only version
#define UTILITY_MOVE_DEFAULT_MEMBERS(pT, pMembers) UTILITY_MOVE_DEFAULT_MEMBERS_DETAIL(pT, pMembers)

#endif

(J'ai supprimé les vrais commentaires, qui sont longs et documentaires.)

Vous spécifiez les bases et/ou les membres de votre classe en tant que liste de préprocesseurs, par exemple:

#include "move_default.hpp"

struct foo
{
    UTILITY_MOVE_DEFAULT_MEMBERS(foo, (x)(str));

    int x;
    std::string str;
};

struct bar : foo, baz
{
    UTILITY_MOVE_DEFAULT_BASES(bar, (foo)(baz));
};

struct baz : bar
{
    UTILITY_MOVE_DEFAULT(baz, (bar), (ptr));

    void* ptr;
};

Et sort un constructeur de mouvement et un opérateur d'affectation de mouvement.

(Soit dit en passant, si quelqu'un sait comment je pourrais combiner les détails dans une macro, ce serait énorme.)

8
GManNickG

VS2010 ne le fait pas car ils n'étaient pas standard au moment de la mise en œuvre.

4
Puppy