Programing

C ++에 가상 생성자가없는 이유는 무엇입니까?

lottogame 2020. 4. 12. 10:10
반응형

C ++에 가상 생성자가없는 이유는 무엇입니까?


C ++에 가상 생성자가없는 이유는 무엇입니까?


말의 입에서 들으십시오. :)

Bjarne Stroustrup의 C ++ 스타일 및 기법 FAQ 가상 생성자가없는 이유는 무엇입니까?

가상 호출은 부분 정보가 주어지면 작업을 수행하는 메커니즘입니다. 특히 "가상"을 사용하면 객체의 정확한 유형이 아닌 인터페이스 만 알고있는 함수를 호출 할 수 있습니다. 개체를 만들려면 완전한 정보가 필요합니다. 특히 만들려는 정확한 유형을 알아야합니다. 결과적으로 "생성자 호출"은 가상 일 수 없습니다.

FAQ 항목은 가상 생성자없이이 목적을 달성 할 수있는 코드를 제공합니다.


가상 함수는 기본적으로 다형성 동작을 제공합니다. 즉, 동적 유형이 참조되는 정적 (컴파일 시간) 유형과 다른 오브젝트를 작업 할 때 오브젝트의 정적 유형 대신 실제 유형의 오브젝트에 적합한 동작을 제공 합니다.

이제 이런 종류의 동작을 생성자에 적용하십시오. 객체를 생성 할 때 정적 유형은 다음과 같은 이유로 항상 실제 객체 유형과 같습니다.

객체를 생성하려면 생성자가 생성하는 객체의 정확한 유형이 필요합니다 [...] 또한 [...] 생성자에 대한 포인터를 가질 수 없습니다

(Bjarne Stroustup (P424 C ++ 프로그래밍 언어 SE))


Smalltalk 또는 Python과 같은 객체 지향 언어와 달리 생성자는 클래스를 나타내는 객체의 가상 메서드입니다. 즉 , 클래스를 나타내는 객체를 만드는 대신 전달할 수 있으므로 GoF 추상 팩토리 패턴이 필요하지 않습니다. C ++은 클래스 기반 언어이며 언어 구성을 나타내는 객체가 없습니다. 클래스는 런타임에 객체로 존재하지 않으므로 가상 메소드를 호출 할 수 없습니다.

내가 본 모든 대형 C ++ 프로젝트는 어떤 형태의 추상 팩토리 또는 리플렉션을 구현했지만 결국 '사용하지 않는 것에 대한 비용을 지불하지 않는다'는 철학에 맞습니다.


내가 생각할 수있는 두 가지 이유 :

기술적 이유

생성자가 종료 된 후에 만 ​​객체가 존재합니다. 가상 테이블을 사용하여 생성자를 전달하려면 가상 테이블에 대한 포인터가있는 기존 객체가 있어야하지만 객체가있는 경우 가상 테이블에 대한 포인터가 어떻게 존재할 수 있습니까 여전히 존재하지 않습니까? :)

논리 이유

다소 다형성적인 행동을 선언 할 때 virtual 키워드를 사용합니다. 그러나 생성자에는 다형성이 없으며 C ++의 생성자 작업은 단순히 객체 데이터를 메모리에 저장하는 것입니다. 가상 테이블 (및 일반적으로 다형성)은 다형성 데이터가 아니라 다형성 동작에 관한 것이므로 가상 생성자를 선언하는 것은 의미가 없습니다.


우리는 단지 생성자가 아닙니다 :-)

struct A {
  virtual ~A() {}
  virtual A * Clone() { return new A; }
};

struct B : public A {
  virtual A * Clone() { return new B; }
};

int main() {

   A * a1 = new B;
   A * a2 = a1->Clone();    // virtual construction
   delete a2;
   delete a1;
}

의미상의 이유를 제외하고는 객체가 생성 될 때까지 vtable이 없으므로 가상 지정이 쓸모가 없습니다.


요약 : C ++ 표준이 지원 컴파일러 너무 열심히 합리적으로 직관적가 아니라 "가상 생성자"s의 표기법과 동작을 지정하지만, 이것에 대한 표준 변경을 왜 때 특히 기능이 이미 깨끗하게 사용하여 구현 될 수있다create()/clone()(참조 이하)? 파이프 라인에서 다른 많은 언어 제안만큼 유용하지는 않습니다.

토론

"가상 생성자"메커니즘을 가정 해 봅시다.

Base* p = new Derived(...);
Base* p2 = new p->Base();  // possible syntax???

위의 첫 번째 줄은 Derived객체를 구성 하므로 *p가상 디스패치 테이블은 두 번째 줄에서 사용할 "가상 생성자"를 합리적으로 제공 할 수 있습니다. (이 페이지에서 "객체가 아직 존재하지 않으므로 가상 구성이 불가능하다"수십 개의 답변 은 불필요하게 구성 대상에 초점을 맞추고 있습니다.)

두 번째 줄 new p->Base()은 다른 Derived객체 의 동적 할당 및 기본 구성을 요청 하는 표기법 가정 합니다.

노트:

  • 컴파일러는 생성자를 호출하기 전에 메모리 할당을 조정해야합니다. 생성자는 일반적 으로 자동 (비공식 "스택") 할당, 정적 (글로벌 / 네임 스페이스 범위 및 클래스 / 함수static객체의 경우) 및 동적 (비공식적으로 "힙")을new사용합니다.

    • 에 의해 생성되는 객체의 크기는 p->Base()일반적으로 컴파일 타임에 알 수 없으므로 동적 할당이 유일하게 접근하는 유일한 방법입니다

  • 동적 할당의 경우 나중에 메모리를 확보 할 수 있도록 포인터 반환 해야합니다delete .

  • 가정 된 표기법은 new동적 할당과 포인터 결과 유형을 강조하기 위해 명시 적으로 나열 됩니다.

컴파일러는 다음을 수행해야합니다.

  • Derived암시 적 virtual sizeof함수 를 호출 하거나 RTTI를 통해 이러한 정보를 사용할 수있게하여 필요한 메모리 양을 찾으십시오.
  • operator new(size_t)메모리 할당 요청
  • Derived()배치로 호출하십시오 new.

또는

  • 동적 할당과 구성을 결합한 함수에 대한 추가 vtable 항목을 만듭니다.

따라서 가상 생성자를 지정하고 구현하는 것이 극복 할 수없는 것처럼 보이지만 백만 달러짜리 질문은 기존 C ++ 언어 기능을 사용하여 가능한 것보다 어떻게 좋을까요? 개인적으로 아래 솔루션보다 이점이 없습니다.


`clone ()`과`create ()`

C ++ FAQ의 "가상 생성자"관용구 문서 포함 virtual create()clone()기본-구조 또는 새로운 동적으로 할당 된 객체를 복사 구축에 방법 :

class Shape {
  public:
    virtual ~Shape() { } // A virtual destructor
    virtual void draw() = 0; // A pure virtual function
    virtual void move() = 0;
    // ...
    virtual Shape* clone() const = 0; // Uses the copy constructor
    virtual Shape* create() const = 0; // Uses the default constructor
};
class Circle : public Shape {
  public:
    Circle* clone() const; // Covariant Return Types; see below
    Circle* create() const; // Covariant Return Types; see below
    // ...
};
Circle* Circle::clone() const { return new Circle(*this); }
Circle* Circle::create() const { return new Circle(); }

create()기본 클래스 / 인터페이스의 virtual함수 서명 과 일치하지만 인수에 대한 인수를 수락 하도록 변경하거나 오버로드 수도 있지만 재정의 인수는 기본 클래스 오버로드 중 하나와 정확히 일치해야합니다. 이러한 명시적인 사용자 제공 기능을 통해 로깅, 계측, 메모리 할당 변경 등을 쉽게 추가 할 수 있습니다.


@stefan의 답변에서 허용되지 않는 이유와 기술적 인 이유를 찾을 수 있습니다. 이제 나에 따르면이 질문에 대한 논리적 인 대답은 다음과 같습니다.

virtual 키워드의 주요 용도는 기본 클래스 포인터가 가리키는 객체 유형을 모르는 경우 다형성 동작을 활성화하는 것입니다.

그러나 이것이 더 원시적 인 방법이라고 생각하십시오. 가상 기능을 사용하려면 포인터가 필요합니다. 그리고 포인터에는 무엇이 필요합니까? 가리킬 물건! (프로그램이 올바르게 실행되는 경우를 고려)

따라서 기본적으로 메모리의 어딘가에 이미 존재하는 객체가 필요합니다 (메모리가 할당 된 방법에 대해서는 신경 쓰지 않으며 컴파일 타임 또는 런타임 중 하나 일 수 있음). 포인터가 해당 객체를 올바르게 가리킬 수 있습니다.

이제, 가리키는 클래스의 객체에 메모리가 할당되는 순간에 대한 상황을 생각해보십시오-> 생성자는 해당 인스턴스 자체에서 자동으로 호출됩니다!

다형성 동작을 사용하려는 경우 생성자가 이미 실행되어 객체를 사용할 수 있기 때문에 생성자가 가상 ​​인 것에 대해 실제로 걱정할 필요가 없음을 알 수 있습니다!


객체 생성은 객체 생성을위한 필수 조건이므로 가상 생성자의 개념은 적합하지 않지만, 완전히 생성되지는 않습니다.

GOF의 'factory method'디자인 패턴은 특정 디자인 상황에서 가상 생성자의 '개념'을 사용합니다.


C ++의 가상 함수는 런타임 다형성의 구현이며 함수 재정의를 수행합니다. 일반적으로 virtual키워드는 동적 동작이 필요할 때 C ++에서 사용됩니다. 객체가 존재할 때만 작동합니다. 반면 생성자는 객체를 만드는 데 사용됩니다. 생성자는 객체 생성시 호출됩니다.

따라서 virtual가상 키워드 정의에 따라 생성자를로 만들면 사용할 기존 객체가 있어야하지만 생성자가 객체를 만드는 데 사용되므로이 경우는 존재하지 않습니다. 따라서 생성자를 가상으로 사용해서는 안됩니다.

따라서 가상 생성자 컴파일러를 선언하려고하면 오류가 발생합니다.

생성자는 가상으로 선언 될 수 없습니다


사람들이 이런 질문을 할 때, 나는 "이것이 실제로 가능하다면 어떻게 될까?" 이것이 무엇을 의미하는지는 잘 모르겠지만 생성되는 객체의 동적 유형을 기반으로 생성자 구현을 재정의 할 수있는 것과 관련이 있다고 생각합니다.

나는 이것과 관련된 많은 잠재적 인 문제를 봅니다. 우선 가상 생성자가 호출 될 때 파생 클래스가 완전히 생성되지 않으므로 구현에 잠재적 인 문제가 있습니다.

둘째, 다중 상속의 경우 어떻게됩니까? 가상 생성자는 아마도 여러 번 호출 될 것이며, 어떤 생성자가 호출되었는지 알 수있는 방법이 필요합니다.

셋째, 일반적으로 시공 시점에서 말하면 객체에 가상 테이블이 완전히 구성되어 있지 않습니다. 즉, 객체의 동적 유형을 시공 할 때 언어 사양을 크게 변경해야합니다. 시각. 그러면 기본 클래스 생성자가 생성시 동적 클래스 유형이 완전히 구성되지 않은 상태에서 다른 가상 함수를 호출 할 수 있습니다.

마지막으로, 다른 사람이 지적했듯이 기본적으로 가상 생성자와 동일한 작업을 수행하는 정적 "create"또는 "init"유형 함수를 사용하여 일종의 가상 생성자를 구현할 수 있습니다.


가상 함수는 포인터 자체가 아닌 포인터가 가리키는 객체의 유형에 따라 함수를 호출하기 위해 사용됩니다. 그러나 생성자는 "호출"되지 않습니다. 객체가 선언 될 때 한 번만 호출됩니다. 따라서 생성자는 C ++에서 가상으로 만들 수 없습니다.


생성자 내에서 가상 함수를 호출해서는 안됩니다. 참조 : http://www.artima.com/cppsource/nevercall.html

또한 가상 생성자가 실제로 필요한지 확실하지 않습니다. 그것없이 다형성 구성을 달성 할 수 있습니다. 필요한 매개 변수에 따라 객체를 구성하는 함수를 작성할 수 있습니다.


하나 이상의 '가상 기능'을 갖는 각 클래스에 대해 가상 테이블 (vtable)이 작성됩니다. 이러한 클래스로 객체가 생성 될 때마다 해당 가상 테이블의베이스를 가리키는 '가상 포인터'가 포함됩니다. 가상 함수 호출이있을 때마다 vtable을 사용하여 함수 주소를 확인합니다. 생성자는 가상 일 수 없습니다. 클래스의 생성자가 실행될 때 메모리에 vtable이 없기 때문에 아직 가상 포인터가 정의되어 있지 않기 때문입니다. 따라서 생성자는 항상 가상이 아니어야합니다.


우리는 단순히 이렇게 말합니다. 우리는 생성자를 상속받을 수 없습니다. 가상은 다형성을 제공하기 때문에 가상으로 선언 할 필요가 없습니다.


가상 메커니즘은 파생 클래스 개체에 대한 기본 클래스 포인터가있는 경우에만 작동합니다. 생성에는 기본 클래스 생성자를 호출하는 기본 규칙이 있습니다. 기본 클래스는 파생 클래스입니다. 가상 생성자가 어떻게 유용하거나 호출 될 수 있습니까? 다른 언어의 기능을 모르지만 가상 생성자가 어떻게 유용하거나 구현 될 수 있는지 알 수 없습니다. 가상 메카니즘이 의미를 갖기 위해서는 시공이 이루어져야하고 다형성 거동의 역학을 제공하는 vtable 구조가 만들어 지도록 시공이 이루어져야합니다.


매우 기본적인 이유가 있습니다. 생성자는 사실상 정적 함수이며 C ++에서는 정적 함수가 가상 일 수 없습니다.

C ++에 대한 경험이 많으면 정적 함수와 멤버 함수의 차이점에 대해 모두 알고 있습니다. 정적 함수는 객체 (인스턴스)가 아니라 CLASS와 연결되므로 "this"포인터가 표시되지 않습니다. '가상'작업을 수행하는 숨겨진 함수 포인터 테이블 인 vtable은 실제로 각 객체의 데이터 멤버이므로 멤버 함수 만 가상 일 수 있습니다.

이제 생성자의 직업은 무엇입니까? "T"생성자는 할당 된대로 T 개체를 초기화합니다. 이것은 멤버 함수 인 것을 자동으로 배제합니다! 객체는 "this"포인터와 vtable을 갖기 전에 존재해야합니다. 즉, 언어가 생성자를 일반 함수로 처리하더라도 (관련되지 않은 이유로 인해 정적 함수) 정적 멤버 함수 여야합니다.

이것을 보는 가장 좋은 방법은 "공장"패턴, 특히 공장 기능을 보는 것입니다. 그들은 당신이 쫓는 일을하고 클래스 T가 팩토리 메소드를 가지고 있다면 항상 정적이라는 것을 알 수 있습니다. 그건 그래야만 해.


C ++ 가상 생성자는 불가능합니다. 예를 들어 생성자를 가상으로 표시 할 수 없습니다.

#include<iostream.h>
using namespace std;
class aClass
{
    public:
        virtual aClass()
        {   
        }  
};
int main()
{
    aClass a; 
}

이 코드는 생성자를 가상으로 선언하려고합니다. 이제 가상 키워드를 사용하는 이유를 이해하도록하겠습니다. 가상 키워드는 런타임 다형성을 제공하는 데 사용됩니다. 예를 들어이 코드를 사용해보십시오.

#include<iostream.h>
using namespace std;
class aClass
{
    public:
        aClass()
        {
            cout<<"aClass contructor\n";
        }
        ~aClass()
        {
            cout<<"aClass destructor\n";
        }

};
class anotherClass:public aClass
{

    public:
        anotherClass()
        {
            cout<<"anotherClass Constructor\n";
        }
        ~anotherClass()
        {
            cout<<"anotherClass destructor\n";
        }

};
int main()
{
    aClass* a;
    a=new anotherClass;
    delete a;   
    getchar(); 
}

main 에서 type of으로 선언 된 포인터에 a=new anotherClass;메모리를 할당합니다. 이것은 생성자 (In )가 자동으로 호출되도록합니다. 따라서 생성자를 virtual로 표시 할 필요가 없습니다. 객체가 생성 될 때 객체의 체인을 따라야합니다. 창조 (즉, 먼저 기초와 파생 클래스). 그러나 삭제하려고 하면 기본 소멸자 만 호출하므로 virtual 키워드를 사용하여 소멸자를 처리해야합니다. 따라서 가상 생성자는 불가능하지만 가상 소멸자는입니다 . 감사합니다.anotherClassaaClassaClassanotherClassdelete a;


C ++에서 생성자가 작동하는 방식과 가상 함수의 의미 / 사용에 대해 논리적으로 생각하면 C ++에서 가상 생성자가 의미가 없음을 알 수 있습니다. C ++에서 virtual을 선언하면 현재 클래스의 하위 클래스로 재정의 할 수 있지만 객체가 생성 될 때 생성자가 호출됩니다. 그 당시에는 클래스의 하위 클래스를 만들 수 없습니다. 생성자를 가상으로 선언 할 필요가 없도록 클래스를 만듭니다.

또 다른 이유는 생성자가 클래스 이름과 이름이 같고 생성자를 가상으로 선언하는 경우 동일한 이름으로 파생 클래스에서 다시 정의해야하지만 두 클래스의 동일한 이름을 가질 수는 없다는 것입니다. 따라서 가상 생성자를 가질 수 없습니다.


  1. 생성자가 호출 될 때까지 해당 시점까지 생성 된 객체는 없지만 객체가 속한 클래스 특정 생성자 가 이미 호출되었으므로 생성 될 객체의 종류를 여전히 알고 있습니다 .

    Virtual함수와 관련된 키워드 는 특정 객체 유형함수가 호출됨을 의미합니다 .

    그래서 내 생각에 이미 객체를 만들 원하는 생성자가 호출 되었기 때문에 가상 생성자를 만들 필요가 없으며 생성자 가상을 만드는 것은 객체 별 생성자 가 이미 호출 되었기 때문에 중복 할 일이라고 생각합니다. 이것은 virtual 키워드를 통해 달성되는 클래스 별 함수호출하는 것과 같습니다 .

    내부 구현은 vptr 및 vtable 관련 이유로 가상 생성자를 허용하지 않지만.


  1. 또 다른 이유는 C ++이 정적으로 유형이 지정된 언어이므로 컴파일 타임에 변수의 유형을 알아야합니다.

    컴파일러는 객체를 만들려면 클래스 유형을 알고 있어야합니다. 생성 될 객체의 유형은 컴파일 타임 결정입니다.

    생성자를 가상으로 만들면 컴파일 타임에 객체 유형을 알 필요가 없습니다 (가상 함수가 제공하는 것입니다). 실제 객체를 알 필요가 없으며 기본 포인터 만 필요합니다. 실제 객체를 가리키면 객체의 유형을 모른 채 뾰족한 객체의 가상 함수를 호출합니다.) 컴파일 타임에 객체의 유형을 모르는 경우 정적으로 유형이 지정된 언어에 위배됩니다. 따라서 런타임 다형성을 달성 할 수 없습니다.

    따라서 컴파일 타임에 객체의 유형을 모르면 생성자가 호출되지 않습니다. 따라서 가상 생성자를 만드는 아이디어는 실패합니다.


Vpointer는 객체 생성시 생성됩니다. vpointer는 객체 생성 전에 존재하지 않습니다. 따라서 생성자를 가상으로 만들 필요가 없습니다.

참고 URL : https://stackoverflow.com/questions/733360/why-do-we-not-have-a-virtual-constructor-in-c

반응형