문제

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 ++ 표준 ~할 수 있었다 합리적으로 직관적이고 컴파일러가 지원하기가 어렵지는 않지만 "가상 생성자"에 대한 표기법과 동작을 지정하지만, 이에 대해 구체적으로 표준 변경을하는 이유는 무엇입니까? 기능 이미 깨끗하게 구현할 수 있습니다 create() / clone() (아래 참조)? 파이프 라인의 다른 많은 언어 제안만큼 유용하지 않습니다.

논의

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

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

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

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

메모:

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

    • 구성 할 물체의 크기 p->Base() 일반적으로 컴파일 타임에서 알 수 없습니다 동적 할당은 의미가있는 유일한 접근법입니다

      • 스택에 런타임 지정된 메모리를 할당 할 수 있습니다 - 예 : GCC의 가변 길이 배열 확장, alloca() -하지만 상당한 비 효율성과 복잡성으로 이어집니다 (예 : 여기 그리고 여기 각기)
  • 동적 할당을 위해 ~ 해야 하다 메모리가 될 수 있도록 포인터를 반환하십시오 deleteD 나중에.

  • 가정 된 표기법은 명시 적으로 나열됩니다 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의 답변에서 허용되지 않는 이유와 기술적 이유를 찾을 수 있습니다. 이제 나에 따른이 질문에 대한 논리적 답은 다음과 같습니다.

가상 키워드의 주요 사용은 기본 클래스 포인터가 지적 할 객체의 유형을 알지 못할 때 다형성 동작을 가능하게하는 것입니다.

그러나 이것이 더 원시적 인 방법이라고 생각하십시오. 가상 기능을 사용하려면 포인터가 필요합니다. 그리고 포인터는 무엇을 요구합니까? 가리키는 대상! (프로그램의 올바른 실행 사례 고려)

따라서 기본적으로 메모리 어딘가에 이미 존재하는 객체가 필요합니다 (메모리가 할당되는 방식에 관심이없고, 컴파일 시간 또는 런타임에있을 수 있음) 포인터가 해당 객체를 올바르게 가리킬 수 있도록합니다.

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

따라서 우리는 생성자가 가상이라는 것에 대해 걱정할 필요가 없다는 것을 알 수 있습니다. 어떤 경우에는 여러분이 다형성 행동을 사용하고 싶기 때문에 생성자가 이미 실행되었을 것입니다.

객체 유형이 객체 생성에 대한 전제 조건이기 때문에 가상 생성자의 개념은 잘 맞지 않지만, 완전한 과도하게 규정되어 있지 않습니다.

GOF의 'Factory Method'설계 패턴은 특정 디자인 상황에서도 적절한 가상 생성자의 '개념'을 사용합니다.

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

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

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

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

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

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

둘째, 여러 상속의 경우 어떻게됩니까? 가상 생성자는 아마도 여러 번 호출 될 것입니다. 아마도 어떤 것을 호출했는지 알아야 할 방법이 필요합니다.

셋째, 일반적으로 시공 시점에 말하면, 객체에는 가상 테이블이 완전히 구성되지 않으므로, 이는 구조시 동적 유형의 객체 유형이 알려질 수 있도록 언어 사양에 큰 변화가 필요하다는 것을 의미합니다. 시각. 그러면 기본 클래스 생성자가 완전히 구성된 동적 클래스 유형으로 구성 시간에 다른 가상 함수를 호출 할 수 있습니다.

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

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

생성자 내에서 가상 기능을 호출해서는 안됩니다. 보다 : http://www.artima.com/cppsource/nevercall.html

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

하나 이상의 '가상 기능'을 갖는 각 클래스마다 가상 테이블 (vtable)이 만들어집니다. 해당 클래스의 객체가 생성 될 때마다 해당 VTABLE의 기반을 가리키는 '가상 포인터'가 포함되어 있습니다. 가상 함수 호출이있을 때마다 vtable은 함수 주소로 해결하는 데 사용됩니다. 클래스의 생성자가 실행되면 메모리에 vtable이 없기 때문에 생성자는 가상이 될 수 없습니다. 아직 가상 포인터가 정의되지 않았 음을 의미합니다. 따라서 생성자는 항상 비가당해야합니다.

우리는 단순히 그렇게 말할 수 없습니다. 우리는 생성자를 물려받을 수 없습니다. 따라서 가상은 다형성을 제공하기 때문에 가상을 선언하는 점이 없습니다.

가상 메커니즘은 파생 클래스 객체에 대한 기반 클래스 포인터가있을 때만 작동합니다. Construction에는 기본적으로 기본 클래스가 도출 된 기본 클래스 생성자의 호출에 대한 자체 규칙이 있습니다. 가상 생성자가 어떻게 유용하거나 호출 될 수 있습니까? 다른 언어가 무엇을하는지 모르겠지만 가상 생성자가 어떻게 유용하거나 구현 될 수 있는지 알 수 없습니다. 가상 메커니즘이 이해하기 위해서는 구조가 이루어져야하며, 다형성 행동의 역학을 제공하는 Vtable 구조가 만들어지기 위해서도 건설이 필요했습니다.

매우 기본적인 이유가 있습니다. 생성자는 효과적으로 정적 함수이며 C ++에서는 정적 기능이 가상이 될 수 없습니다.

C ++에 대한 경험이 많으면 정적 및 멤버 기능의 차이점에 대해 모두 알고 있습니다. 정적 함수는 객체 (인스턴스)가 아닌 클래스와 관련이 있으므로 "이"포인터가 보이지 않습니다. '가상'작업을하는 숨겨진 기능 테이블은 실제로 각 객체의 데이터 구성원이기 때문에 멤버 기능 만 가상 일 수 있습니다.

이제 생성자의 작업은 무엇입니까? 이름은 "t"생성자가 할당 된 상태에서 t 객체를 초기화합니다. 이것은 자동으로 멤버 함수 인 것을 배제합니다! 객체는 "이"포인터와 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(); 
}

메인 a=new anotherClass; 메모리를 할당합니다 anotherClass 포인터에서 a 유형으로 선언되었습니다 aClass이로 인해 두 생성자가 두 가지를 일으 킵니다 (in aClass 그리고 anotherClass) 자동으로 호출하려면 생성자를 가상으로 표시 할 필요가 없습니다. 객체가 생성되기 때문에 생성 체인을 따라야합니다 (예 : 먼저베이스와 파생 클래스). 그러나 우리가 삭제하려고 할 때 delete a; 기본 소멸자 만 호출하게되므로 가상 키워드를 사용하여 소멸자를 처리해야합니다. 따라서 가상 생성자는 불가능하지만 가상 파괴자는.감사

생성자의 작동 방식과 가상 함수의 의미/사용이 C ++에 대해 논리적으로 생각한다면 가상 생성자가 C ++에서 의미가 없다는 것을 알게 될 것입니다. C ++에서 가상을 선언한다는 것은 현재 클래스의 하위 클래스에 의해 상환 될 수 있음을 의미하지만, 객체가 생성 될 때 생성자가 호출됩니다. 생성자 가상을 선언 할 필요가 없도록 클래스를 만드는 것입니다.

그리고 또 다른 이유는, 생성자가 클래스 이름과 동일한 이름을 가지기 때문입니다. 생성자를 가상으로 선언하면 같은 이름으로 파생 클래스에서 재정의되어야하지만 두 클래스의 이름을 동일한 이름을 가질 수 없습니다. 따라서 가상 생성자를 가질 수는 없습니다.

  1. 생성자가 호출 될 때, 그 시점까지 생성 된 객체는 없지만, 우리는 여전히 생성 될 객체의 종류를 알고 있습니다. 특정 생성자 객체가 속한 클래스 중에서 이미 호출되었습니다.

    Virtual 함수와 관련된 키워드는 의미합니다 특정 객체 유형의 함수 부름을받을 것입니다.

    따라서 내 생각은 이미 객체가 만들어지는 원하는 생성자가 호출되었고 생성자 가상을 만드는 데 가상 생성자를 만들 필요가 없다고 말합니다. 객체 별 생성자 이미 호출되었고 이것은 전화와 동일합니다. 클래스 별 함수 가상 키워드를 통해 달성됩니다.

    내부 구현은 VPTR 및 VTable 관련 이유에 대한 가상 생성자를 허용하지는 않지만.


  1. 또 다른 이유는 C ++가 정적으로 입력 한 언어이며 컴파일 타임에서 변수의 유형을 알아야하기 때문입니다.

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

    생성자를 가상으로 만드는 경우 컴파일 타임에서 객체의 유형을 알 필요가 없음을 의미합니다 (이것이 가상 기능이 제공하는 것입니다. 실제 객체를 알 필요가 없으며 기본 포인터가 필요합니다. 지적 실제 객체는 객체의 유형을 알지 못하고 뾰족한 객체의 가상 함수를 호출하고 컴파일 타임에 객체의 유형을 모른다면 정적으로 입력 한 언어에 위배됩니다. 따라서, 런타임 다형성은 달성 될 수 없다.

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

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

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top