leesangwon0114

I am Research Engineer. Currently working in KT.

C++Intermediate 18. temporary object#1

13 Nov 2018 » c++

codenuri 강석민 강사 강의 내용기반으로 정리한 내용입니다.


임시객체의 개념과 수명

#include <iostream>
using namespace std;

int main()
{
    int a = 1, b = 2, c = 3;

    int sum = a + b + c; // int temp = a + b;
                         // int sum = temp + c;
}

컴파일러가 컴파일하다가 필요에 의해 만든 temp 같은 변수를 임시객체라함

전통적인 임시객체는 컴파일러에 의해 만들어지는 임시 메모리 공간

C++은 이 개념에 확장해 사용자가 임시객체를 만들 수 있는 등 다양한 기법 존재


Point.h

#include <iostream>
class Point
{
public:
    int x, y;

    Point (int a = 0, int b = 0)
    {
        std::cout << "Point()" << std::endl;
    }
    Point (const Point&)
    {
        std::cout << "Point(constPoint&)" << std::endl;
    }
    ~Point()
    {
        std::cout << "~Point()" << std::endl;
    }
};

#include <iostream>
#include "Point.h"
using namespace std;;

int main()
{
    Point p1(1,1); // 이름 : p1, 파괴 : 함수{}을 벗어날 때.
                   // named object
    Point(1,1);    // 이름 : 없다, 파괴 : 문장의 끝(;)
                   // 임시객체 : 클래스이름()
                   // unnamed object
    cout << "end" << endl;
}

객체는 Point() end ~Point() 순으로 출력

임시객체는 Point() ~Point() end 순으로 출력

#include <iostream>
#include "Point.h"
using namespace std;;

int main()
{
    Point(1, 1), cout << "X" << endl;
    cout << "end" << endl;
}

Point() X ~Point() end 출력


임시 객체의 특징

#include <iostream>
#include "Point.h"
using namespace std;

int main()
{
    Point pt(1,1); // pt: 이름있는 객체
    pt.x = 10; // ok.

    Point(1,1).x = 10; // error. 특징 1. 임시객체는 lvalue 가 될 수 없다.

    Point* p1 = &pt; // ok.

    Point* p2 = &Point(1,1); // error. 특징 2. 임시객체는 주소를 구할 수 없다.

    Point& r1 = pt; // ok.
    Point& r2 = Point(1, 1); // error. 특징 3. 임시객체는 non const 참조할 수 없다.(cl컴파일러만 예외로 확장문법을 사용해 허용, Za옵션으로 확장 문법 끄면됨)

    const Point& r3 = pt; // ok.
    const Point& r4 = Point(1,1); // ok. 특징 4. 임시객체는 const 참조로 참조할 수 있다. -> 임시객체의 수명이 r4의 수명과 동일해 진다.

    // C++11 rvalue reference
    Point&& r5 = Point(1, 1); // ok. 특징 5. 임시객체는 rvalue 참조로 참조할 수 있다.
    r5.x = 10; // ok.
}

임시 객체와 함수

임시객체를 어떻게 활용하고 장점이 있는지 살펴보자

#include <iostream>
#include "Point.h"
using namespace std;

// 임시객체와 함수 인자
void foo(const Point& p)
{

}

int main()
{
    // foo의 인자로 주는데 문장의 끝에 도달해서 파괴됨
    Point pt(1, 1);
    foo(pt);

    // 임시객체를 사용한 인자전달
    foo(Point(1,1));

    cout << "end" << endl;
}

단지 함수인자를 전달하기 위한 객체를 만들면 임시객체를 이용한 인자전달이 메모리에 효과적

sort(x, x+10, greater()); 와 같이 stl도 임시객체 이용(greater)

따라서 받을 때는 무조건 const 참조로 받아야함(일반 참조로 받으면 에러 발생)

-> ms 확장문법은 가능하지만 안쓰는 것이 좋음


#include <iostream>
#include "Point.h"
using namespace std;

Point foo()
{
    Point pt(1, 1); // 2. 생성자
    return pt; // return Point(pt) 임시객체 생성 -> pt와 같게 만들려고 3. 복사생성자
}

int main()
{
    Point ret(0,0); // 1. 생성자

    ret = foo(); // 4. 임시객체가 대입연산자를 사용해 ret에 대입됨

    cout << "end" << endl; // 5. end 출력

    // ret 소멸자가 불림
}

Point() Point() Point(const Point&) ~Point() ~Point() end ~Point() 순으로 출력됨

임시객체는 성능저하의 원인으로 return을 만들 때 아래와 같이 만듬

#include <iostream>
#include "Point.h"
using namespace std;

Point foo()
{
    return Point(1,1);
}

int main()
{
    Point ret(0,0);

    ret = foo();

    cout << "end" << endl;
}

Point() Point() ~Point() end ~Point() 순으로 출력

-> RVO(Return Value Optimization)

요즘 컴파일러는 첫번째 처럼 만들어도 컴파일러가 바꾸어 버림

-> NRVO(Named RVO) g++은 이런식으로 코드를 바꾸어서 복사생성자가 불리지 않았던 것임

-> MS 도 O2라는 최적화 옵션을 주면 NRVO 적용해 복사생성자가 불리지 않도록 할 수 있음