C ++에서 새로운 생성자없이 생성자 호출


142

사람들이 자주 C ++로 객체를 만드는 것을 보았습니다.

Thing myThing("asdf");

이 대신에 :

Thing myThing = Thing("asdf");

적어도 관련된 템플릿이없는 한 (gcc 사용) 작동하는 것 같습니다. 내 질문은 지금 첫 줄이 맞습니까? 그렇다면 사용해야합니까?


25
어느 형태도 새로운 것이 아닙니다.
Daniel Daranas

13
두 번째 형식은 복사 생성자를 사용하므로 동일하지 않습니다.
Edward Strange

나는 그것으로 조금 연주, 매개 변수가없는 생성자와 템플릿을 사용하는 경우 첫 번째 방법은 때때로 실패하는 것 같습니다.
Nils

1
Ouh와 저는 "Nice Question"배지를 받았습니다.
Nils

답변:


153

두 줄은 사실 정확하지만 미묘하게 다른 일을합니다.

첫 번째 줄은 format의 생성자를 호출하여 스택에 새 객체를 만듭니다 Thing(const char*).

두 번째는 조금 더 복잡합니다. 본질적으로 다음을 수행합니다.

  1. Thing생성자를 사용하여 유형의 객체를 만듭니다.Thing(const char*)
  2. Thing생성자를 사용하여 유형의 객체를 만듭니다.Thing(const Thing&)
  3. ~Thing()1 단계에서 생성 한 개체를 호출 합니다.

7
이러한 유형의 작업은 최적화되어 있으므로 성능 측면에서 크게 다르지 않습니다.
M. Williams

14
당신의 발걸음이 옳다고 생각하지 않습니다. Thing myThing = Thing(...)대입 연산자를 사용하지 않고, 여전히 말처럼 복사 구성 Thing myThing(Thing(...))이며 기본 구성을 포함하지 않습니다 Thing(편집 : 게시물이 수정되었습니다)
AshleysBrain

1
따라서 두 번째 줄은 명백한 이유없이 자원을 낭비하기 때문에 잘못되었다고 말할 수 있습니다. 물론 첫 번째 인스턴스를 만드는 것이 일부 부작용에 의도적 인 것일 수도 있지만 (스타일 적으로) 더 나빠질 수도 있습니다.
MK.

3
아니요, @Jared, 보장되지 않습니다. 그러나 컴파일러가 해당 최적화를 수행하도록 선택하더라도 복사 생성자는 구현 또는 호출되지 않은 경우에도 여전히 액세스 가능해야합니다 (즉, 보호되거나 개인용이 아님).
Rob Kennedy

3
복사 생성자가 부작용이 있어도 복사가 생략 될 수 있습니다. 내 답변 참조 : stackoverflow.com/questions/2722879/…
Douglas Leeder

31

나는 당신이 실제로 의미하는 두 번째 줄로 가정합니다 :

Thing *thing = new Thing("uiae");

이는 새로운 동적 객체 (동적 바인딩 및 다형성에 필요)를 만들고 주소를 포인터에 저장 하는 표준 방법입니다 . 귀하의 코드는 JaredPar가 설명 한대로, 즉 두 개의 객체 (하나는 a를 전달 const char*하고 다른 하나는 a 를 전달 const Thing&)를 생성 한 다음 ~Thing()첫 번째 객체 ( const char*하나) 에서 소멸자 ( ) 를 호출합니다 .

대조적으로, 이것은 :

Thing thing("uiae");

현재 범위를 종료하면 자동으로 제거되는 정적 객체를 만듭니다.


1
불행히도, 이는 실제로 auto_ptr, unique_ptr 또는 관련을 사용하는 대신 새로운 동적 객체를 생성하는 가장 일반적인 방법입니다.
Fred Nurk

3
OP의 질문은 정확했습니다.이 답변은 다른 문제와 관련이 있습니다 (
@JaredPar

21

컴파일러는 두 번째 양식을 첫 번째 양식으로 최적화 할 수 있지만 반드시 그럴 필요는 없습니다.

#include <iostream>

class A
{
    public:
        A() { std::cerr << "Empty constructor" << std::endl; }
        A(const A&) { std::cerr << "Copy constructor" << std::endl; }
        A(const char* str) { std::cerr << "char constructor: " << str << std::endl; }
        ~A() { std::cerr << "destructor" << std::endl; }
};

void direct()
{
    std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl;
    A a(__FUNCTION__);
    static_cast<void>(a); // avoid warnings about unused variables
}

void assignment()
{
    std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl;
    A a = A(__FUNCTION__);
    static_cast<void>(a); // avoid warnings about unused variables
}

void prove_copy_constructor_is_called()
{
    std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl;
    A a(__FUNCTION__);
    A b = a;
    static_cast<void>(b); // avoid warnings about unused variables
}

int main()
{
    direct();
    assignment();
    prove_copy_constructor_is_called();
    return 0;
}

gcc 4.4의 출력 :

TEST: direct
char constructor: direct
destructor

TEST: assignment
char constructor: assignment
destructor

TEST: prove_copy_constructor_is_called
char constructor: prove_copy_constructor_is_called
Copy constructor
destructor
destructor

정적 캐스트의 무효화 목적은 무엇입니까?
Stephen Cross

1
@Stephen 사용하지 않는 변수에 대한 경고를 피하십시오.
Douglas Leeder

10

간단히 말해서 두 줄 모두 '신규'처럼 힙이 아닌 스택에 객체를 만듭니다. 두 번째 줄에는 실제로 복사 생성자에 대한 두 번째 호출이 포함되므로 피해야합니다 (주석에 표시된대로 수정해야 함). 작은 개체에는 스택이 빠르기 때문에 가능한 한 많이 사용해야합니다. 그러나 개체가 스택 프레임보다 오래 생존 할 경우 분명히 잘못된 선택입니다.


힙 에서와는 달리 스택에서 객체를 인스턴스화하는 것의 차이에 익숙하지 않은 사람들에게는 ( new 를 사용하고 new 사용하지 않음 ) 여기 좋은 스레드가 있습니다.
edmqkk

2

이상적으로는 컴파일러가 두 번째를 최적화하지만 필수는 아닙니다. 첫 번째 방법이 가장 좋습니다. 그러나 C ++에서 스택과 힙의 차이점을 이해하는 것은 매우 중요합니다.


컴파일러에서 복사 생성자에 부작용 (예 : I / O)이 없음을 보장 할 수 있습니까?
Stephen Cross

@Stephen-복사 생성자가 I / O를 수행하는지 여부는 중요하지 않습니다. 내 답변을 참조하십시오 stackoverflow.com/questions/2722879/…
Douglas Leeder

알다시피, 컴파일러는 두 번째 양식을 첫 번째 양식으로 바꿀 수 있으므로 복사 생성자에 대한 호출을 피할 수 있습니다.
Stephen Cross

2

나는 그것을 조금 연주했고 생성자가 인수를 취하지 않으면 구문이 상당히 이상해 보입니다. 예를 들어 보겠습니다.

#include <iostream> 

using namespace std;

class Thing
{
public:
    Thing();
};

Thing::Thing()
{
    cout << "Hi" << endl;
}

int main()
{
    //Thing myThing(); // Does not work
    Thing myThing; // Works

}

따라서 브라켓없이 Thing myThing을 작성하면 실제로 생성자가 호출되는 반면 Thing myThing ()은 컴파일러가 함수 포인터 또는 무언가를 만들고 싶은 것을 만듭니다.


6
이것은 C ++에서 잘 알려진 구문 모호성입니다. "int rand ()"를 작성할 때 컴파일러는 "int를 작성하고 기본 초기화"또는 "함수 rand 선언"을 의미하는지 알 수 없습니다. 규칙은 가능할 때마다 후자를 선택한다는 것입니다.
jpalecek

1
그리고 이것은 여러분, 가장 독창적 인 파싱 입니다.
Marc.2377

2

JaredPar 답변에 추가

임시 물체가있는 1 개의 특이한 ctor, 2 차 함수와 유사한 ctor

다른 컴파일러로 http://melpon.org/wandbox/ 어딘가에이 소스를 컴파일하십시오 .

// turn off rvo for clang, gcc with '-fno-elide-constructors'

#include <stdio.h>
class Thing {
public:
    Thing(const char*){puts(__FUNCTION__ );}
    Thing(const Thing&){puts(__FUNCTION__ );}   
    ~Thing(){puts(__FUNCTION__);}
};
int main(int /*argc*/, const char** /*argv*/) {
    Thing myThing = Thing("asdf");
}

그리고 당신은 결과를 볼 수 있습니다.

ISO / IEC 14882 2003-10-15에서

8.5, 12 부

첫 번째, 두 번째 구성을 직접 초기화라고합니다.

12.1, 13 부

기능 표기법 유형 변환 (5.2.3)을 사용하여 해당 유형의 새 객체를 만들 수 있습니다. [참고 : 구문은 생성자를 명시 적으로 호출하는 것처럼 보입니다. ] ... 이런 식으로 생성 된 객체의 이름은 없습니다. [참고 : 12.2는 임시 객체의 수명에 대해 설명합니다. ] [참고 : 명시 적 생성자 호출은 lvalue를 생성하지 않습니다. 3.10을 참조하십시오. ]


RVO에 대한 정보 :

12 특수 멤버 함수 / 12.8 클래스 객체 복사 / 15 부

특정 기준이 충족되면 객체 의 복사 생성자 및 / 또는 소멸자가 부작용이 있더라도 구현에서 클래스 객체의 복사 구성을 생략 할 수 있습니다 .

주석에서 컴파일러 플래그로 끄고 복사 동작을 확인하십시오.)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.