본문 바로가기
-----ETC-----/C++ 성능 최적화 및 고급 테크닉 시리즈

[C++ 성능 최적화 및 고급 테크닉] Day 6: 불필요한 복사 방지 (copy elision)

by cogito21_cpp 2024. 8. 1.
반응형

불필요한 복사란?

불필요한 복사는 객체를 복사하는 과정에서 발생하는 오버헤드입니다. 이는 성능 저하와 메모리 사용 증가를 초래할 수 있습니다. C++에서는 이러한 불필요한 복사를 방지하기 위한 다양한 기법을 제공합니다.

 

불필요한 복사 방지 기법

1. RVO(Return Value Optimization)와 NRVO(Named Return Value Optimization)

RVO와 NRVO는 컴파일러가 반환값을 최적화하여 불필요한 복사를 방지하는 기법입니다.

 

예제 코드

#include <iostream>

class MyClass {
public:
    MyClass() {
        std::cout << "Constructor" << std::endl;
    }
    MyClass(const MyClass&) {
        std::cout << "Copy Constructor" << std::endl;
    }
    MyClass& operator=(const MyClass&) {
        std::cout << "Copy Assignment" << std::endl;
        return *this;
    }
    ~MyClass() {
        std::cout << "Destructor" << std::endl;
    }
};

MyClass createObject() {
    MyClass obj;
    return obj; // NRVO 적용
}

int main() {
    MyClass obj = createObject(); // RVO 적용
    return 0;
}

 

위 코드는 MyClass 객체를 반환할 때 RVO와 NRVO가 적용되어 불필요한 복사를 방지합니다.

 

2. 이동 시멘틱스 (Move Semantics)

C++11부터 도입된 이동 시멘틱스를 사용하면 복사 대신 이동을 통해 성능을 최적화할 수 있습니다. 이동 생성자와 이동 할당 연산자를 정의하여 자원 소유권을 이전합니다.

 

예제 코드

#include <iostream>
#include <vector>

class MyClass {
public:
    MyClass() : data(new int[1000000]) {
        std::cout << "Constructor" << std::endl;
    }
    ~MyClass() {
        delete[] data;
        std::cout << "Destructor" << std::endl;
    }

    // Move Constructor
    MyClass(MyClass&& other) noexcept : data(other.data) {
        other.data = nullptr;
        std::cout << "Move Constructor" << std::endl;
    }

    // Move Assignment Operator
    MyClass& operator=(MyClass&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            other.data = nullptr;
            std::cout << "Move Assignment" << std::endl;
        }
        return *this;
    }

private:
    int* data;
};

MyClass createObject() {
    MyClass obj;
    return obj;
}

int main() {
    MyClass obj = createObject(); // Move Constructor
    MyClass obj2 = std::move(obj); // Move Assignment
    return 0;
}

 

이 예제에서 이동 생성자와 이동 할당 연산자를 사용하여 객체 이동을 최적화합니다.

 

3. std::move와 std::forward

std::move는 객체를 왼값(lvalue)에서 오른값(rvalue)으로 캐스팅합니다. 이를 통해 이동 시멘틱스를 사용할 수 있습니다.

 

예제 코드

#include <iostream>
#include <utility>

class MyClass {
public:
    MyClass() {
        std::cout << "Constructor" << std::endl;
    }
    MyClass(const MyClass&) {
        std::cout << "Copy Constructor" << std::endl;
    }
    MyClass(MyClass&&) noexcept {
        std::cout << "Move Constructor" << std::endl;
    }
};

void process(MyClass obj) {
    // Process the object
}

int main() {
    MyClass obj;
    process(std::move(obj)); // Move the object
    return 0;
}

 

이 예제에서 std::move를 사용하여 객체를 이동합니다.

 

실습 문제

문제 1: 이동 시멘틱스를 사용하여 불필요한 복사 방지

다음 코드에서 이동 시멘틱스를 사용하여 불필요한 복사를 방지해보세요.

 

main.cpp

#include <iostream>
#include <vector>

class MyClass {
public:
    MyClass() : data(new int[1000000]) {
        std::cout << "Constructor" << std::endl;
    }
    ~MyClass() {
        delete[] data;
        std::cout << "Destructor" << std::endl;
    }

    // Copy Constructor
    MyClass(const MyClass& other) : data(new int[1000000]) {
        std::copy(other.data, other.data + 1000000, data);
        std::cout << "Copy Constructor" << std::endl;
    }

    // Copy Assignment Operator
    MyClass& operator=(const MyClass& other) {
        if (this != &other) {
            std::copy(other.data, other.data + 1000000, data);
            std::cout << "Copy Assignment" << std::endl;
        }
        return *this;
    }

private:
    int* data;
};

MyClass createObject() {
    MyClass obj;
    return obj;
}

int main() {
    MyClass obj = createObject();
    return 0;
}

 

해답:

main.cpp (최적화)

#include <iostream>
#include <vector>

class MyClass {
public:
    MyClass() : data(new int[1000000]) {
        std::cout << "Constructor" << std::endl;
    }
    ~MyClass() {
        delete[] data;
        std::cout << "Destructor" << std::endl;
    }

    // Move Constructor
    MyClass(MyClass&& other) noexcept : data(other.data) {
        other.data = nullptr;
        std::cout << "Move Constructor" << std::endl;
    }

    // Move Assignment Operator
    MyClass& operator=(MyClass&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            other.data = nullptr;
            std::cout << "Move Assignment" << std::endl;
        }
        return *this;
    }

private:
    int* data;
};

MyClass createObject() {
    MyClass obj;
    return obj;
}

int main() {
    MyClass obj = createObject();
    return 0;
}

 

이제 여섯 번째 날의 학습을 마쳤습니다. 불필요한 복사를 방지하는 기법과 이동 시멘틱스를 이해하고, 실습 문제를 통해 이해를 높였습니다.

질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "데이터 로컬리티와 캐시 친화적 코딩"에 대해 학습하겠습니다.

반응형