Linux g++에서 class copy constructor이 호출되지 않습니다...ㅠㅠ
글쓴이: jic5760 / 작성시간: 월, 2016/10/31 - 8:28오전
소스는.. 아주 간단합니다.
class Test { public: Test() { std::cout << "Test Create" << std::endl; } ~Test() { std::cout << "Test Destroy" << std::endl; } Test(const Test& ref) { std::cout << "Test Copy 1" << std::endl; } Test& operator=(const Test& ref) { std::cout << "Test Copy 2" << std::endl; return *this; } }; Test testfunc1() { Test a; return a; } int main() { Test a = testfunc1(); return 0; }
컴파일 환경은 G++ 4.8.5 입니다.
옵티마이저 옵션도 -O0 을 줘서 껐습니다..
그런데 왜 Test Copy 가 출력되지 않을까요???
Windows Visual C++ 에서는 잘 됩니다...
* 출력 :
Test Create
끝...
만약
Test a; a = testfunc1();
을 하면
Test Create
Test Create
Test Copy 2
Test Destroy
이렇게 잘 됩니다...
Forums:
제가 학부 OOP 수업 실습 하다가 발견하고 경악을
제가 학부 OOP 수업 실습 하다가 발견하고 경악을 금치 못했던 문제로군요.
답을 달아 드리고 싶은데 오늘 제가 일이 너무 많고 피곤해서 어렵겠습니다. 내일까지 다른 분이 답 안 주시면 제가 쓰도록 하죠.
ㅎㅎ
아래에 다른 분께서 답변해주셨네요~ 감사합니다~
부연설명이나 더 하도록 하죠.
저도 학부 때 OOP 들으면서 복사 생성자 함수 호출을 추적하다가 발견한 문제였죠.
일반적으로 컴파일러 최적화는 사용자 정의 함수 호출을 제거할 수 없습니다.
이러한 함수 호출은 Side effect가 있을 수 있기 때문이고, 그걸 함부로 제거하면 프로그램 동작이 크게 바뀔 수 있으니까요. (파일 입출력, 네트워크 송수신, DB 기록 등)
질문자님이 작성하신 복사 생성자의 동작, 즉 콘솔에 메시지를 출력하는 것 역시도 원래는 생략되어선 안 될 Side effect의 한 예입니다.
함수에서 값을 반환할 때는 분명히 클래스 객체가 복사됩니다. (C++03 std 12.8.1) 이 경우 복사 생성자(Copy constructor) 혹은 대입 연산자(Copy assignment operator)가 호출되죠. 이 두 함수는 사용자가 정의할 수 있으므로 위에서 언급한 일반론이 적용됩니다. 즉 프로그래머는 C++ 컴파일러가 이러한 함수 호출을 (최적화 조건에 상관 없이) 보존해 줄 것이라고 기대하게 됩니다.
... 그리고 C++ 표준은 이러한 기대를 통렬하게 배반합니다. 이 맛에 C++ 씁니다!
번역은 솔직히 별로 자신 없습니다만 할 수 있는 데까지 해 보죠.
네. 귀찮아서 번역을 하다 말았는데 중요한 부분은 다 나왔죠. 질문자님이 올리신 코드가 첫째 경우에 정확히 해당되므로, 컴파일러는 복사 생성자 및 소멸자를 날려버리고 함수
testfunc1
의 자동 변수a
를 함수main
에서 반환값을 받아 오는 변수인a
자리에 그냥 만들어버리는 겁니다.C++03 표준에서 이 부분에 대한 예제 코드도 실어 놨는데 질문자님 코드와 비슷하네요.
어떻게 보면 이후 C++11에 도입된 이동 의미론(move semantics) 중에서도 특수한 경우라고 볼 수도 있겠네요. 곧 복제된 뒤 소멸될 것이 분명한 객체를 아예 복사본이 위치할 자리에 먼저 만들어 버림으로써, 복사 생성자 하나와 소멸자 하나의 비용을 절약한 것입니다. 어쨌든 생성자 한 번에 소멸자 한 번이 대응되는 불변식은 변함이 없습니다.
이런 최적화를 도입한 이유야 뭐 분명합니다. C++ 짜다 보면 함수 매개변수로 객체를 전달할 때는 선택의 여지가 좀 있지만, 값을 반환해야만 하는 상황에서는 다른 수가 없는 경우가 종종 있잖아요. 예컨대
operator+
를int
의 그것과 비슷한 의미로 오버로딩하는 경우, 함수 안에서 새 값을 만들어서 반환해야 하는데 이건 포인터로도 안되고 참조자로도 안되죠. 이 경우 이러한 최적화를 통해 그나마 복사 생성자와 소멸자 한 쌍 값을 절약할 수 있죠. 위안이 되지 않습니까.이 최적화는 C++03 표준에서는 이름을 지어 주지 않았지만, C++11 표준에서도 분명히 살아남아서 이제 이름까지 지어졌습니다. "copy elision"이라고 하더군요. 물론 더 오래 전부터 통용되던 명칭인 RVO(Return Value Optimization, 반환값 최적화)도 계속 통용될겁니다. 아무튼, C++11 표준에서는 이게 허용되는 상황이 2개에서 4개로 늘었는데, 번역하기 귀찮으니 관심 있으시면 찾아가서 보세요.(C++11 std 12.8.31) 짧게 언급하자면, 추가된 두 가지 상황은 예외 처리와 관련된 것입니다.
이러한 최적화는 "허용되는" 것이지 "강제되는" 것이 아니므로, 정말로 할지 말지는 컴파일러 개발사 마음입니다. g++처럼 좋은 컴파일러는 옵션으로도 제공하는 모양이지만요. 그런데 제가 존경하는, Effective C++의 저자 Scott Meyers님은 이렇게 말씀하시더군요. (More Effective C++, 곽용재 편역, 163p)
하지만 그렇다고는 해도, 복사 생성자와 소멸자 안에 Side effect가 일어나게 작성할 경우 컴파일러가 최적화 여부에 따라 프로그램의 동작이 크게 바뀔 수 있다는 점을 염두에 두기는 해야겠죠.
Return Value Optimization
RVO 및 생성자 관련된 최적화 때문입니다. 이 부분은 컴파일러에 의존합니다.
without -fno-elide-constructors option
with -fno-elide-constructors option
http://stackoverflow.com/questions/8758152/disabling-gs-return-value-optimisation
감사합니다!
옵티마이저만 끈다고 해결되는게 아니었네요..ㅠㅠ
정말 감사드립니다!
복사생성자 테스트해야할 일이 있었는데 덕분에 해결되었네요~^^
댓글 달기