연쇄 할 때 복사를 제거하는 방법?


10

아래의 작은 예제와 같은 체인 유형의 클래스를 만들고 있습니다. 멤버 함수를 연결할 때 복사 생성자가 호출되는 것 같습니다. 복사 생성자 호출을 제거하는 방법이 있습니까? 아래의 장난감 예제에서, 나는 단지 임시적인 것만을 다루고 있으므로 "표준에 의해서가 아니라 논리적으로"소멸해야한다는 것이 명백하다. 제거를 복사하는 두 번째 최선의 선택은 이동 생성자를 호출하는 것이지만, 그렇지 않습니다.

class test_class {
    private:
    int i = 5;
    public:
    test_class(int i) : i(i) {}
    test_class(const test_class& t) {
        i = t.i;
        std::cout << "Copy constructor"<< std::endl;
    }
    test_class(test_class&& t) {
        i = t.i;
        std::cout << "Move constructor"<< std::endl;
    }
    auto& increment(){
        i++;
        return *this;
    }
};
int main()
{
    //test_class a{7};
    //does not call copy constructor
    auto b = test_class{7};
    //calls copy constructor
    auto b2 = test_class{7}.increment();
    return 0;
}

편집 : 몇 가지 설명. 1. 이것은 최적화 수준에 의존하지 않습니다. 2. 실제 코드에는 ints보다 더 복잡한 (예 : 힙 할당) 객체가 있습니다.


어떤 최적화 수준을 컴파일에 사용합니까?
JVApen

2
auto b = test_class{7};복사 생성자를 호출하지 않습니다. 실제로 test_class b{7};는 컴파일러가이 경우를 인식하기에 충분히 현명하므로 복사를 쉽게 제거 할 수 있습니다. 에 대해서도 마찬가지입니다 b2.
일부 프로그래머 친구

도시 된 예에서, 이동과 복사 사이에 실제로는 큰 차이가 없을 수 있으며 모든 사람이 이것을 인식하지는 않습니다. 큰 벡터와 같은 것을 붙인다면 다른 문제 일 수 있습니다. 일반적으로 이동은 많은 힙 메모리 등을 사용하는 것과 같은 유형을 사용하는 리소스에 대해서만 의미가 있습니다.
darune

이 예는 생각이 든다. std::cout사본 관리자에 실제로 I / O ( )가 있습니까? 그것 없이는 복사본을 최적화해야합니다.
Rusyx

@rustyx, std :: cout을 제거하고 복사 생성자를 명시 적으로 만드십시오. 이는 복사 제거가 std :: cout에 종속되지 않음을 보여줍니다.
DDaniel

답변:


7
  1. 부분 답변 ( b2제자리에 구성되지는 않지만 복사본 구성을 이동 구성으로 바꿉니다) : increment연관된 인스턴스의 값 범주 에서 멤버 함수를 오버로드 할 수 있습니다 .

    auto& increment() & {
        i++;
        return *this;
    }
    
    auto&& increment() && {
        i++;
       return std::move(*this);
    }

    이 원인

    auto b2 = test_class{7}.increment();

    일시적 b2이기 때문에 이동 구문을 작성 test_class{7}하고 &&과부하를 test_class::increment호출합니다.

  2. 실제 내부 구성 (예 : 이동 구성)을 위해 모든 특별 및 비 특별 멤버 기능을 constexpr버전 으로 전환 할 수 있습니다 . 그런 다음 할 수 있습니다

    constexpr auto b2 = test_class{7}.increment();

    그리고 당신은 이사를하거나 사본을 지불하지 않아도됩니다. 이것은 단순 test_class하지만 가능 하지만 constexpr멤버 기능을 허용하지 않는 일반적인 시나리오에서는 불가능 합니다.


두 번째 대안은 또한 b2수정 불가능하다.
일부 프로그래머 친구

1
@Someprogrammerdude 좋은 지적입니다. 나는 constinit거기에 갈 길이 라고 생각한다 .
lubgr

함수 이름 뒤에 앰퍼샌드의 목적은 무엇입니까? 나는 전에 그것을 본 적이 없다.
필립 넬슨

1
@PhilipNelson @ ref-qualifiers 이며 멤버 함수 this와 동일한 const기능을 지시 할 수 있습니다
kmdreko

1

기본적으로, 를 할당 하려면 생성자, 즉 copy 또는 move를 호출해야합니다 . 이것은 함수의 양쪽에서 동일한 별개의 객체로 알려진 와 다릅니다. 또한 는 포인터와 매우 유사한 공유 객체를 할 수 있습니다.


가장 간단한 방법은 아마도 복사 생성자를 완전히 최적화하는 것입니다. 값 설정은 이미 컴파일러에 의해 최적화되었으며 최적화 std::cout할 수없는 값입니다.

test_class(const test_class& t) = default;

(또는 그냥 복사 및 이동 생성자를 모두 제거하십시오)

라이브 예


문제는 기본적으로 참조와 관련이 있으므로이 방법으로 복사를 중지하려는 경우 솔루션이 객체에 대한 참조를 반환하지 않을 수 있습니다.

  void increment();
};

auto b = test_class{7};//does not call copy constructor
b.increment();//does not call copy constructor

세 번째 방법은 처음에는 복사 제거에 의존하는 것입니다. 그러나 작업을 하나의 함수로 다시 작성하거나 캡슐화해야하므로 문제를 완전히 피해야합니다 (이것은 원하는 것이 아닐 수도 있지만 다른 사용자를위한 솔루션) :

auto b2 = []{test_class tmp{7}; tmp.increment().increment().increment(); return tmp;}(); //<-- b2 becomes 10 - copy constructor not called

네 번째 방법은 명시 적으로 호출 된 이동을 대신 사용하는 것입니다.

auto b2 = std::move(test_class{7}.increment());

또는 이 답변 에서 볼 수 있듯이 .


오닐 @ 다른 경우의 생각 - 타이, 수정
darune
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.