ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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)  (0) 2020.06.29

    댓글

Designed by Tistory.