std :: vector <AbstractClass>를 선언 할 수없는 이유는 무엇입니까?


88

C #에서 개발하는 데 꽤 많은 시간을 보냈는데, 인터페이스로 사용할 목적으로 추상 클래스를 선언하면이 추상 클래스의 벡터를 인스턴스화하여 자식 클래스의 인스턴스를 저장할 수 없다는 것을 알았습니다.

#pragma once
#include <iostream>
#include <vector>

using namespace std;

class IFunnyInterface
{
public:
    virtual void IamFunny()  = 0;
};

class FunnyImpl: IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};

class FunnyContainer
{
private:
    std::vector <IFunnyInterface> funnyItems;
};

추상 클래스의 벡터를 선언하는 줄은 MS VS2005에서 다음 오류를 발생시킵니다.

error C2259: 'IFunnyInterface' : cannot instantiate abstract class

IFunnyInterface를 다음으로 대체하는 명백한 해결 방법이 있습니다.

class IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        throw new std::exception("not implemented");
    }
};

이것이 C ++ 현명한 해결 방법입니까? 그렇지 않은 경우이 문제를 해결하는 데 도움이 될 수있는 boost와 같은 타사 라이브러리가 있습니까?

읽어 주셔서 감사합니다!

안토니

답변:


127

추상 클래스를 인스턴스화 할 수 없으므로 추상 클래스의 벡터가 작동하지 않습니다.

그러나 추상 클래스에 대한 포인터 벡터를 사용할 수 있습니다.

std::vector<IFunnyInterface*> ifVec;

이것은 또한 실제로 다형성 동작을 사용할 수있게합니다. 클래스가 추상이 아니더라도 값으로 저장하면 객체 슬라이싱 문제가 발생 합니다 .


5
또는 개체 수명을 수동으로 처리하지 않으려면 std :: vector <std :: tr1 :: shared_ptr <IFunnyInterface>>를 사용할 수 있습니다.
Sergey Teplyakov

4
또는 더 나은 방법은 boost :: ptr_vector <>입니다.
Roel

7
또는 이제 std :: vector <std :: unique_ptr <IFunnyInterface >>입니다.
Kaz Dragon

21

추상 클래스의 인스턴스를 만들 수 없기 때문에 추상 클래스 유형의 벡터를 만들 수 없으며 std :: vector 저장소 값 (예 : 인스턴스)과 같은 C ++ 표준 라이브러리 컨테이너를 만들 수 없습니다. 이렇게하려면 추상 클래스 유형에 대한 포인터 벡터를 만들어야합니다.

가상 함수 (처음에 추상 클래스를 원하는 이유)는 포인터 나 참조를 통해 호출 될 때만 작동하기 때문에 해결 방법이 작동하지 않습니다. 참조 벡터도 만들 수 없으므로 이것이 포인터 벡터를 사용해야하는 두 번째 이유입니다.

C ++와 C #은 공통점이 거의 없다는 것을 알아야합니다. C ++를 배우려는 경우 처음부터 시작하는 것으로 생각 하고 Koenig 및 Moo의 Accelerated C ++ 와 같은 좋은 전용 C ++ 자습서를 읽어야합니다 .


게시물에 대한 답글과 함께 책을 추천 해주셔서 감사합니다!
BlueTrin

그러나 추상 클래스의 벡터를 선언 할 때 추상 클래스를 생성하도록 요청하는 것이 아니라 해당 클래스의 추상이 아닌 하위 클래스를 보유 할 수있는 벡터 일 뿐입니 까? 벡터 생성자에 숫자를 전달하지 않는 한 생성 할 추상 클래스의 인스턴스 수를 어떻게 알 수 있습니까?
조나단.

6

이 경우에는이 코드도 사용할 수 없습니다.

std::vector <IFunnyInterface*> funnyItems;

또는

std::vector <std::tr1::shared_ptr<IFunnyInterface> > funnyItems;

FunnyImpl과 IFunnyInterface간에 IS A 관계가없고 개인 상속으로 인해 FUnnyImpl과 IFunnyInterface간에 암시 적 변환이 없기 때문입니다.

다음과 같이 코드를 업데이트해야합니다.

class IFunnyInterface
{
public:
    virtual void IamFunny()  = 0;
};

class FunnyImpl: public IFunnyInterface
{
public:
    virtual void IamFunny()
    {
        cout << "<INSERT JOKE HERE>";
    }
};

1
대부분의 사람들은 내가 생각하는 사적 상속을 살펴 보았다 :) 그러나 OP를 더 혼동하지 말자 :)
Roel

1
네. 특히 "C #에서 개발하는 데 꽤 많은 시간을 보냈습니다"라는 주제의 시작 문구 이후에 (개인 상속이 전혀 없음).
Sergey Teplyakov

6

전통적인 대안은 vector 이미 언급 한 것처럼 포인터 입니다.

감사하는 사람들을 Boost위해 매우 흥미로운 라이브러리가 제공됩니다.Pointer Containers 이 는 작업에 완벽하게 적합하며 포인터가 암시하는 다양한 문제에서 벗어날 수 있습니다.

  • 평생 관리
  • 반복자의 이중 역 참조

이것은 a보다 훨씬 낫습니다. vector성능과 인터페이스 측면에서 스마트 포인터 .

이제 계층 구조를 변경하는 세 번째 대안이 있습니다. 사용자의 단열 효과를 높이기 위해 다음 패턴을 여러 번 사용했습니다.

class IClass;

class MyClass
{
public:
  typedef enum { Var1, Var2 } Type;

  explicit MyClass(Type type);

  int foo();
  int bar();

private:
  IClass* m_impl;
};

struct IClass
{
  virtual ~IClass();

  virtual int foo();
  virtual int bar();
};

class MyClass1: public IClass { .. };
class MyClass2: public IClass { .. };

이것은 매우 간단 Pimpl하고 Strategy패턴으로 풍성한 관용구 의 변형입니다 .

물론 "진정한"개체를 직접 조작하고 싶지 않은 경우에만 작동하며 딥 복사가 포함됩니다. 그래서 당신이 원하는 것이 아닐 수도 있습니다.


1
Boost 레퍼런스와 디자인 패턴에 감사드립니다
BlueTrin

2

벡터의 크기를 조정하려면 기본 생성자와 클래스 크기를 사용해야하므로 구체적이어야합니다.

다른 제안대로 포인터를 사용할 수 있습니다.


1

std :: vector는 유형을 포함하기 위해 메모리를 할당하려고합니다. 클래스가 순전히 가상 인 경우 벡터는 할당해야하는 클래스의 크기를 알 수 없습니다.

해결 방법을 사용하면 다음을 컴파일 할 수 있습니다. vector<IFunnyInterface> 있지만 그 안에서 FunnyImpl을 조작 할 수는 없을 것이라고 생각합니다. 예를 들어 IFunnyInterface (추상 클래스)의 크기가 20 (정말 모르겠습니다)이고 FunnyImpl의 크기가 30 인 경우 멤버와 코드가 더 많기 때문에 결국 20의 벡터에 30을 맞추려고합니다.

해결책은 "new"를 사용하여 힙에 메모리를 할당하고 포인터를 저장하는 것입니다. vector<IFunnyInterface*>


나는 이것이 답이라고 생각하지만, 그것은 컨테이너 내에서 무슨 일이 일어날 지 여기서 설명 GF 응답 및 객체 분할에 대 한 조회
BlueTrin

이 답변은 무슨 일이 일어날 지 설명했지만 '슬라이싱'이라는 단어를 사용하지 않았으므로이 답변이 맞습니다. ptr의 벡터를 사용하면 슬라이싱이 발생하지 않습니다. 이것이 처음에 ptr을 사용하는 요점입니다.
Roel

-2

이 정말 슬픈 제한의 근본 원인은 생성자가 가상이 될 수 없다는 사실이라고 생각합니다. 그 때문에 컴파일러는 컴파일 시간을 알지 못하면 객체를 복사하는 코드를 생성 할 수 없습니다.


2
이것은 근본 원인이 아니며 "슬픈 제한"도 아닙니다.

제한이 아니라고 생각하는 이유를 설명해주세요. 능력이 있으면 좋을 것입니다. 그리고 프로그래머가 컨테이너에 포인터를 놓아야하고 삭제에 대해 걱정할 때 약간의 오버 헤드가 발생합니다. 동일한 용기에 크기가 다른 물체를 넣으면 성능이 저하된다는 데 동의합니다.
David Gruzman

가상 함수는 보유한 객체의 유형에 따라 전달됩니다. 생성자의 요점은 아직 객체가 없다는 것 입니다. 정적 가상 기능을 가질 수없는 이유와 관련이 있습니다. 또한 객체가 없습니다.
MSalters

클래스 컨테이너의 템플릿은 객체가 아니라 클래스 팩토리이고 생성자는 자연스러운 부분이라고 말할 수 있습니다.
David Gruzman
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.