기본 이동 할당 / 이동 생성자가없는 이유는 무엇입니까?


89

저는 단순한 프로그래머입니다. 내 클래스 멤버 변수는 대부분 POD 유형과 STL 컨테이너로 구성됩니다. 이 때문에 기본적으로 구현되는 할당 연산자 나 복사 생성자를 거의 작성할 필요가 없습니다.

여기에 std::move움직일 수없는 물체에 사용하면 할당 연산자를 활용하므로 std::move완벽하게 안전합니다.

나는 단순한 프로그래머이기 때문에 컴파일러가 단순히 " this->member1_ = std::move(other.member1_);..." 로 구현할 수 있었기 때문에 내가 작성하는 모든 클래스에 이동 생성자 / 할당 연산자를 추가하지 않고 이동 기능을 활용하고 싶습니다.

그러나 그렇지 않습니다 (적어도 Visual 2010에서는 아님), 이것에 대한 특별한 이유가 있습니까?

더 중요한 것은; 이 문제를 해결할 방법이 있습니까?

업데이트 : GManNickG의 답변을 보면 그는 이에 대한 훌륭한 매크로를 제공합니다. 모르는 경우 이동 시맨틱을 구현하면 스왑 멤버 함수를 제거 할 수 있습니다.


5
당신은 당신이 컴파일러가 기본 이동 ctor에 생성 할 수 있습니다 알
aaronman

3
std :: move는 이동을 수행하지 않고 단순히 l- 값에서 r- 값으로 캐스트합니다. 이동은 여전히 ​​이동 생성자에 의해 수행됩니다.
Owen Delahoy

1
당신은 말하고 MyClass::MyClass(Myclass &&) = default;있습니까?
Sandburg

예, 요즘 :)
Viktor Sehr

답변:


76

이동 생성자 및 할당 연산자의 암시 적 생성은 논쟁의 여지가 있었고 C ++ 표준의 최근 초안에 주요 수정이 있었으므로 현재 사용 가능한 컴파일러는 암시 적 생성과 관련하여 다르게 동작 할 것입니다.

문제의 역사에 대한 자세한 내용 은 2010 WG21 논문 목록을 참조 하고 "mov"를 검색 하십시오 .

현재 사양 (N3225, 11 월부터)에는 다음과 같이 명시되어 있습니다 (N3225 12.8 / 8).

클래스 정의가 X이동 생성자를 명시 적으로 선언하지 않으면 다음과 같은 경우에만 기본값으로 암시 적으로 선언됩니다.

  • X 사용자가 선언 한 복사 생성자가 없습니다.

  • X 사용자가 선언 한 복사 할당 연산자가 없습니다.

  • X 사용자가 선언 한 이동 할당 연산자가 없습니다.

  • X 사용자가 선언 한 소멸자가 없습니다.

  • 이동 생성자는 암시 적으로 삭제 된 것으로 정의되지 않습니다.

이동 할당 연산자가 암시 적으로 기본값으로 선언되는시기를 지정하는 유사한 언어가 12.8 / 22에 있습니다. Bjarne Stroustrup의 논문 N3201 : Moving right along 에서 제안한 해결 방법 중 하나를 주로 기반으로하는 N3203 : 암시 적 이동 생성 조건 강화 에서 암시 적 이동 생성의 현재 사양을 지원하기 위해 만들어진 전체 변경 목록을 찾을 수 있습니다 .


4
여기에 암시 적 (이동) 생성자 / 할당에 대한 관계를 설명하는 다이어그램이 포함 된 작은 기사를 썼습니다. mmocny.wordpress.com/2010/12/09/implicit-move-wont-go
mmocny 2011

그래서 가상으로 지정하기 위해 다형성 기본 클래스에서 빈 소멸자를 정의해야 할 때마다 이동 생성자와 할당 연산자도 명시 적으로 정의해야합니다. (.
someguy

@James McNellis : 이전에 시도한 작업이지만 컴파일러가 좋아하지 않는 것 같습니다. 바로이 답장에 오류 메시지를 게시하려고했지만, 오류를 재현하려고 시도한 결과 cannot be defaulted *in the class body*. 그래서 외부에서 소멸자를 정의했고 작동했습니다. :). 그래도 조금 이상합니다. 누구에게 설명이 있습니까? 컴파일러는 gcc 4.6.1
someguy

3
C ++ 11이 비준되었으므로이 답변에 대한 업데이트를 얻을 수 있습니까? 어떤 행동이 이겼는지 궁금합니다.
Joseph Garvin 2014

2
@Guy Avraham : 제가 말한 것은 (7 년이 지났습니다) 사용자가 선언 한 소멸자가 있으면 (빈 가상 가상이라도) 이동 생성자가 기본값으로 암시 적으로 선언되지 않는다는 것입니다. 나는 그것이 복사 의미론을 초래할 것이라고 생각합니까? (나는 몇 년 동안 C ++를 건드리지 않았다.) James McNellis는 그것이 virtual ~D() = default;작동하고 여전히 암시 적 이동 생성자를 허용해야한다고 언급했다 .
someguy

13

암시 적으로 생성 된 이동 생성자는 표준으로 고려되었지만 위험 할 수 있습니다. Dave Abrahams의 분석을 참조하십시오 .

그러나 결국 표준에는 상당한 제한 목록이 있지만 이동 생성자와 이동 할당 연산자의 암시 적 생성이 포함되었습니다.

클래스 X의 정의가 이동 생성자를 명시 적으로 선언하지 않으면 다음 경우에만 기본값으로 암시 적으로 선언됩니다.
X에 사용자 선언 복사 생성자
가없는 경우 , — X에 사용자 선언 복사 할당 연산자가없는 경우 ,
— X에는 사용자가 선언 한 이동 할당 연산자
가 없습니다. — X에는 사용자가 선언 한 소멸자가 없습니다. 그리고
— 이동 생성자는 암시 적으로 삭제 된 것으로 정의되지 않습니다.

하지만 그게 전부는 아닙니다. ctor를 선언 할 수 있지만 여전히 삭제 된 것으로 정의됩니다.

암시 적으로 선언 된 복사 / 이동 생성자는 해당 클래스의 인라인 공용 멤버입니다. X 클래스에 대한 기본 복사 / 이동 생성자는 X가 다음과 같은 경우 삭제 (8.4.3)로 정의됩니다.

— 사소하지 않은 대응 생성자를 가진 변형 멤버이고 X는 공용체와 같은 클래스입니다.
— 과부하 해결 (13.3) 때문에 복사 / 이동할 수없는 클래스 유형 M (또는 그 배열)의 비 정적 데이터 멤버 M의 해당 생성자에 적용하면 기본 생성자에서 삭제되거나 액세스 할 수없는 모호성 또는 함수가
발생합니다. B의 해당 생성자에 적용된 오버로드 해결 (13.3)로 인해 복사 / 이동할 수없는 직접 또는 가상 기본 클래스 B , 기본 생성자에서 삭제되거나 액세스 할 수없는 모호성 또는 함수 (
— 삭제되거나 액세스 할 수없는 소멸자가있는 유형의 모든 직접 또는 가상 기본 클래스 또는 비 정적 데이터 멤버
— 복사 생성자의 경우 rvalue 참조 유형
의 비 정적 데이터 멤버 또는 — 이동 생성자의 경우 이동 생성자가없고 단순하지 않은 유형의 비 정적 데이터 멤버 또는 직접 또는 가상 기본 클래스 복사 가능.


현재 작업 초안은 특정 조건에서 암시 적 이동 생성을 허용하며,이 결의안은 대부분 아브라함의 우려를 해결한다고 생각합니다.
James McNellis 2011 년

Tweak 2와 Tweak 3 사이의 예에서 어떤 움직임이 깨질 수 있는지 잘 모르겠습니다. 설명해 주시겠습니까?
Matthieu M.

@Matthieu M .: Tweak 2와 Tweak 3은 모두 망가졌습니다. Tweak 2에는 무브 액터에 의해 깨질 수있는 불변성을 가진 개인 멤버가 있습니다. Tweak 3에서는 클래스 자체에 private 멤버 없지만 private 상속을 사용하기 때문에 base의 public 및 protected 멤버가 파생 된 private 멤버가되어 동일한 문제가 발생합니다.
Jerry Coffin 2011 년

1
나는 이동 생성자가에서 불변하는 클래스를 어떻게 깨뜨릴 것인지 정말로 이해하지 못했습니다 Tweak2. 나는 그것이 Number이동되고 vector복사 될 것이라는 사실과 관련이 있다고 생각 하지만 ... 확실하지 않습니다 : / 문제가 Tweak3.
Matthieu M.

당신이 준 링크가 죽은 것 같나요?
늑대

8

(지금은 어리석은 매크로 작업 중입니다 ...)

그래, 나도 그 길을 갔다. 매크로는 다음과 같습니다.

// 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

// 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

(길이와 다큐멘터리 인 실제 댓글은 삭제했습니다.)

클래스의 기본 및 / 또는 멤버를 전 처리기 목록으로 지정합니다. 예를 들면 다음과 같습니다.

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

그리고 이동 생성자와 이동 할당 연산자가 나옵니다.

(제쳐두고, 내가 세부 사항을 하나의 매크로로 결합하는 방법을 아는 사람이 있다면 그것은 부풀어 오를 것입니다.)


대단히 감사합니다. 저는 멤버 변수의 수를 인수로 전달해야한다는 점을 제외하면 매우 유사합니다.
Viktor Sehr

1
@Viktor : 문제 없습니다. 너무 늦지 않았다면 다른 답변 중 하나를 수락 된 것으로 표시해야한다고 생각합니다. 내 것은 실제 질문에 대한 답이 아니라 "그런데 여기에 방법"에 가깝습니다.
GManNickG 2011 년

1
매크로를 올바르게 읽고 있다면 컴파일러가 기본 이동 멤버를 구현하자마자 위의 예제를 복사 할 수 없게됩니다. 명시 적으로 선언 된 이동 멤버가있는 경우 복사 멤버의 내재적 생성이 금지됩니다.
Howard Hinnant 2011 년

@Howard : 괜찮습니다. 그때까지는 일시적인 해결책입니다. :)
GManNickG 2011 년

GMan :이 매크로는 스왑 기능이있는 경우 moveconstructor \ assign을 추가합니다.
Viktor Sehr

4

VS2010은 구현 당시 표준이 아니기 때문에이를 수행하지 않습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.