-
4. 이동가능 객체(Movable Type)Modern C++/Move Semantics 2020. 6. 30. 01:15
클래스를 이동 가능하게 만들면 많은 장점들이 있고 이동 가능한 사용자 정의 타입은 표준 라이브러리 유틸과도 잘 동작한다.
이동 연산을 지원하는 클래스를 만드는 경우 코드의 성능, 안정성이나 구현 할 수 있는 방식이 증가한다.
이동 가능한 사용자 타입을 정의하게 되면 표준 라이브리를 사용하는 중에도 이동 연산에 대한 이점을 얻을 수 가 있다.
C++11 이전에 컴파일러는 기본 생성자, 소멸자, 복사 생성자, 복사 할당 연산자를 자동으로 추가해 주었는데, C++11 이 후는 여기에 이동 생성자와 이동 할당 연산자가 추가되었다.
여기서는 유리수를 나타내는 Rational 클래스를 통해서 이동 연산이 추가되었을 때 어떤 장점이 있는지 살펴본다.
#pragma once #include <iostream> class Rational { public: Rational(); Rational(int numerator, int denominator = 1); int numerator() const { return numerator_; } int denominator() const { return denominator_; } Rational(const Rational& rhs); // Copy Constructor Rational& operator = (const Rational& rhs); //Copy Assignment friend std::ostream& operator << (std::ostream& oss, const Rational& r); private: void simplify(); private: int numerator_; int denominator_; };
더보기Rational.cpp
#include "Rational.h" #include <vector> #include <cmath> Rational::Rational(): numerator_(0), denominator_(1){ } Rational::Rational(int numerator, int denominator) : numerator_(numerator), denominator_(denominator){ simplify(); } Rational::Rational(const Rational& rhs) { std::cout << "Copy Construct" << std::endl; numerator_ = rhs.numerator_; denominator_ = rhs.denominator_; } Rational& Rational::operator = (const Rational& rhs) { std::cout << "Assignment" << std::endl; numerator_ = rhs.numerator_; denominator_ = rhs.denominator_; return *this; } std::ostream& operator << (std::ostream& oss, const Rational& r) { return oss << r.numerator_ << "/" << r.denominator_; } void Rational::simplify() { }
위의 Rational은 복사 연산자를 구현했으므로 컴파일러가 자동으로 이동 연산자를 생성해 주지 않는다.
#include "Rational.h" #include <vector> int main() { Rational r0(2, 1); Rational r1(2, 2); std::vector<Rational> rationals; rationals.reserve(10); rationals.push_back(r0); //왼값 rationals.push_back(Rational(2, 3)); //오른값 rationals.push_back(std::move(r0)); //오른값 std::cout << r0 << std::endl; return 0; }
이동 가능하지 않은 클래스를 벡터에 추가하는 경우 push_back 함수에 전달된 인자가 오른값 / 왼값에 상관없이 모두 복사해서 새로운 객체를 생성한 후 벡터에 추가된다.
이동 연산자를 추가하지 않은 경우 실행 결과 이제 이동 생성자와 이동 할당 연산자를 Rational 클래스에 추가해 결과가 어떻게 변화는지를 살펴보자..
class Rational {
public:
....
Rational(Rational&& rhs); //이동 생성자
Rational& operator = (Ratinal&& rhs); //이동 할당자
...
}
Rational::Rational(Rational&& rhs) {
std::cout << "Move Construct" << std::endl;
numerator_ = std::exchange(rhs.numerator_, 0);
denominator_ = std::exchange(rhs.denominator_, 1);
}
Rational& Rational::operator = (Rational&& rhs) {
std::cout << "Move Assignment" << std::endl;
numerator_ = std::exchange(rhs.numerator_, 0);
denominator_ = std::exchange(rhs.denominator_, 1);
return *this;
}이동 가능하도록 구현한 Rational 객체를 위와 동일하게 벡터에 추가하는 경우 결과는 아래와 같다.
이동 연산자를 추가한 실행결과 위에 살펴본 것 처럼 개발자가 이동가능하게 객체를 설계하게되면 표준 라이브러리를 사용하는 데 있어 이동 연산으로 인해 복사를 피하면서 취할 수 있는 여러 이점들이 있다.
전체 구현은 아래와 같다.
더보기Rational.h
#pragma once #include <iostream> class Rational { public: Rational(); Rational(int numerator, int denominator = 1); int numerator() const { return numerator_; } int denominator() const { return denominator_; } Rational(const Rational& rhs); // Copy Constructor Rational& operator = (const Rational& rhs); //Copy Assignment Rational(Rational&& rhs); //Move Constructor Rational& operator = (Rational&& rhs); // Move Assignment friend std::ostream& operator << (std::ostream& oss, const Rational& r); private: void simplify(); private: int numerator_; int denominator_; };
Rational.cpp
#include "Rational.h" #include <vector> #include <cmath> void simplify(int& numenrator, int& denominator); Rational::Rational(): numerator_(0), denominator_(1){ } Rational::Rational(int numerator, int denominator) : numerator_(numerator), denominator_(denominator){ simplify(); } Rational::Rational(const Rational& rhs) { std::cout << "Copy Construct" << std::endl; numerator_ = rhs.numerator_; denominator_ = rhs.denominator_; } Rational& Rational::operator = (const Rational& rhs) { std::cout << "Assignment" << std::endl; numerator_ = rhs.numerator_; denominator_ = rhs.denominator_; return *this; } Rational::Rational(Rational &&rhs) { std::cout << "Move Construct" << std::endl; numerator_ = std::exchange(rhs.numerator_, 0); denominator_ = std::exchange(rhs.denominator_, 1); } Rational & Rational::operator=(Rational &&rhs) { std::cout << "Move Assignment" << std::endl; numerator_ = std::exchange(rhs.numerator_, 0); denominator_ = std::exchange(rhs.denominator_, 1); return *this; } std::ostream& operator << (std::ostream& oss, const Rational& r) { return oss << r.numerator_ << "/" << r.denominator_; } void Rational::simplify() { int temp_n = numerator_; int temp_d = denominator_; ::simplify(temp_n, temp_d); numerator_ = temp_n; denominator_ = temp_d; } void get_factors(int num, std::vector<int>& factor_set) { if(num != 1) { factor_set.push_back(num); } for(int i = 2; i <= std::sqrt(static_cast<double>(num)); i++) { if(num % i == 0) { factor_set.push_back(i); factor_set.push_back(num/i); } } } void simplify(int& numenrator, int& denominator) { int temp_n = numenrator; int temp_d = denominator; int small, temp; std::vector<int> factor_set; if(temp_n == temp_d) { numenrator = 1; denominator = 1; return; } if(temp_n == -temp_d) { numenrator = -1; denominator = 1; return; } if(temp_n == 0){ denominator = 1; return; } small = std::abs(temp_n) < std::abs(temp_d)? std::abs(temp_n) : std::abs(temp_d); get_factors(small, factor_set); for(size_t i =0; i < factor_set.size(); i++) { temp = factor_set[i]; while(temp_n % temp == 0 && temp_d % temp == 0) { temp_n /= temp; temp_d /= temp; } } numenrator = temp_n; denominator = temp_d; }
main.cpp
#include "Rational.h" #include <vector> int main() { Rational r0( 10, 5); Rational r1(2, 2); std::vector<Rational> rationals; rationals.reserve(10); rationals.push_back(r0); rationals.push_back(Rational(2, 3)); rationals.push_back(std::move(r0)); std::cout << r0 << std::endl; return 0; }
CMakeLists.txt
cmake_minimum_required(VERSION 3.16) project(movable_rational) set(CMAKE_CXX_STANDARD 17) add_executable(movable_rational main.cpp Rational.cpp Rational.h)
'Modern C++ > Move Semantics' 카테고리의 다른 글
5. C++11 객체 생성 규칙 Rule of Five (0) 2020.07.01 3. 완벽 전달(Perfect Forwarding) (1) 2020.06.29 2. std::move (0) 2020.06.29 1. 이동 의미론(Move Semantics) (1) 2020.06.29