C ++에서 모든 객체의 기본이 권장되지 않는 이유


76

Stroustrup은 "모든 클래스 (객체 클래스)에 대한 고유 한 기반을 즉시 발명하지 마십시오. 일반적으로 대부분 / 대부분의 클래스를 사용하지 않고도 더 잘 수행 할 수 있습니다." (C ++ 프로그래밍 언어 제 4 판, 1.3.4 절)

모든 것을위한 기본 클래스가 일반적으로 나쁜 생각 인 이유는 무엇이며 언제 만드는 것이 합리적입니까?


16
C ++은 Java가 아니기 때문에 ... 강제로 시도해서는 안됩니다.
AK_

10

26
또한, "주요 의견 기반"에 대한 최종 투표에 동의하지 않습니다. 이 질문과 연결된 SO 질문에 대한 답변이 모두 입증 되었기 때문에 이에 대해 설명 할 수있는 매우 구체적인 이유가 있습니다.

2
민첩성의 "필요하지 않을"원칙입니다. 특정 요구 사항을 이미 확인하지 않은 경우 수행하지 마십시오 (할 때까지).
Jool

3
@AK_ : 귀하의 의견에 "멍청한만큼"이 없습니다.
DeadMG

답변:


75

그 객체가 기능을 위해 무엇을 가지고 있을까요? 자바에서 모든 Base 클래스에는 toString, hashCode & equality 및 monitor + condition 변수가 있습니다.

  • ToString은 디버깅에만 유용합니다.

  • hashCode는 해시 기반 컬렉션에 저장하려는 경우에만 유용합니다 (C ++의 기본 설정은 해시 함수를 템플릿 매개 변수로 컨테이너에 전달하거나 std::unordered_*완전히 피하고 std::vector순서가없는 일반 목록을 사용하는 것입니다).

  • 기본 객체가없는 평등은 컴파일 타임에 도움이 될 수 있습니다. 같은 유형이 없으면 같을 수 없습니다. C ++에서 이것은 컴파일 시간 오류입니다.

  • 모니터 및 조건 변수는 경우에 따라 명시 적으로 포함하는 것이 좋습니다.

그러나 필요한 것이 더 있으면 유스 케이스가 있습니다.

예를 들어 QT에는 QObject스레드 선호도, 부모-자식 소유권 계층 및 신호 슬롯 메커니즘의 기반을 형성 하는 루트 클래스가 있습니다. 또한 QObject에 대한 포인터로 강제 사용하지만 Qt의 많은 클래스 는 신호 슬롯 (특히 설명의 값 유형)이 필요하지 않기 때문에 QObject를 상속하지 않습니다 .


7
Java에 기본 클래스가있는 주된 이유를 언급하는 것을 잊어 버렸습니다. 제네릭을 사용하기 전에 컬렉션 클래스가 작동하려면 기본 클래스가 필요했습니다. 모든 것 (내부 저장소, 매개 변수, 반환 값)이 입력되었습니다 Object.
Aleksandr Dubinsky

1
@AleksandrDubinsky : 그리고 제네릭은 구문 설탕 만 추가했습니다.
중복 제거기

4
해시 코드, 평등 및 모니터 지원은 Java에서도 디자인 실수라고 주장합니다. 모든 물체를 자물쇠로 만드는 것이 좋은 생각이라고 누가 생각 했습니까?!
usr

1
그래,하지만 아무도 원하지 않아 마지막으로 객체를 잠글 필요 가 있었을 때 별도의 잠금 객체를 인스턴스화 할 수 없었습니다. 매우 드물며 모든 것에 부담을줍니다. 자바 사람들은 그 당시 스레드 안전에 대해 잘 이해하지 못했으며 모든 객체가 잠금 상태이며 현재 사용되지 않는 스레드 안전 컬렉션에 대한 증거로 사용되었습니다. 스레드 안전성은 객체 별 속성이 아니라 전역 속성입니다.
usr

2
" 당신은 해시 기반 컬렉션을 저장하려면 해시 코드에만 유용합니다. (C ++의 환경 설정은 표준 : : 벡터와 일반 정렬되지 않은 목록입니다) 하는 진짜 반론 ' _hashCode아니다'다른 컨테이너를 사용 '이 아니라 가리키는 C ++ std::unordered_map은 구현을 제공하기 위해 요소 클래스 자체를 요구하지 않고 템플릿 인수를 사용하여 해싱을 수행합니다. 즉, C ++의 다른 모든 좋은 컨테이너 및 리소스 관리자와 마찬가지로 방해가되지 않습니다. 누군가가 나중에 어떤 맥락에서 필요할 때를 대비 하여 함수 또는 데이터로 모든 객체를 오염시키지 않습니다 .
underscore_d

100

모든 객체가 공유하는 기능이 없기 때문입니다. 이 클래스에는 모든 클래스에 적합한 인터페이스가 없습니다.


10
답을 간단히하기 위해 +1인데, 이것이 유일한 이유입니다.
BWG

7
내가 경험 한 대규모 프레임 워크에서 공통 기본 클래스는 <whatever> 컨텍스트에서 원하는 직렬화 및 리플렉션 인프라를 제공합니다. Meh. 결과적으로 사람들은 데이터 및 메타 데이터와 함께 많은 분열을 직렬화하고 데이터 형식을 너무 크고 복잡하게 만들었습니다.
dmckee

19
@ dmckee : 나는 또한 직렬화와 리플렉션이 보편적으로 유용한 요구는 아니라고 주장합니다.
DeadMG

16
@DeadMG : "하지만 모든 것을 저장해야한다면 어떻게해야합니까?"
deworde

8
잘 모르겠습니다. 당신은 따옴표로 묶고 모든 대문자를 사용하며 사람들은 농담을 볼 수 없습니다. @MSalters : 글쎄, 그것은 쉬워야한다. 최소한의 상태를 가지고있다. 재귀 루프를 입력하지 않고 목록에 내 이름을 쓸 수 있습니다.
deworde

25

키가 큰 상속 계층 구조를 만들 때마다 Fragile Base Class (Wikipedia.) 의 문제가 발생하는 경향이 있습니다 .

작은 별개의 (상이하고 분리 된) 상속 계층 구조가 많으면이 문제가 발생할 가능성이 줄어 듭니다.

모든 객체를 하나의 거대한 상속 계층 구조의 일부로 만들면 실제로이 문제가 발생할 수 있습니다.


6
기본 클래스 (Java "java.lang.Object")에 다른 메소드를 호출하는 메소드가 포함되어 있지 않으면 취약한 기본 클래스 문제가 발생할 수 없습니다.
Martin Rosenau

3
강력하고 유용한 기본 클래스입니다!
Mike Nakis

9
@MartinRosenau ... 마스터베이스 클래스없이 C ++에서 할 수있는 것처럼!
gbjbaanb

5
@ DavorŽdralo 따라서 C ++은 기본 함수 ( "DebugPrint"와 같은 의미가 아닌 "연산자 <<")에 대한 어리석은 이름을 가지고 있지만 Java는 예외없이 클래스를 작성하는 모든 클래스에 대해 기본 클래스의 괴물이 있습니다. 나는 C ++의 사마귀를 더 좋아한다고 생각합니다.
Sebastian Redl

4
@ DavorŽdralo : 함수의 이름은 관련이 없습니다. 구문 이미지 cout.print(x).print(0.5).print("Bye\n")– 그것은 힌지가 아닙니다 operator<<.
MSalters

24

때문에:

  1. 사용하지 않는 것에 대해 비용을 지불해서는 안됩니다.
  2. 이러한 함수는 참조 기반 유형 시스템보다 값 기반 유형 시스템에서 의미가 떨어집니다.

모든 종류의 virtual기능을 구현 하면 가상 테이블이 생겨나 고 많은 (대부분의) 상황에서 필요하지 않은 오브젝트 별 공간 오버 헤드가 필요합니다.

toString가상적으로 구현 하는 것은 반환 할 수있는 유일한 것은 객체 주소입니다.이 주소는 Java와 달리 사용자에게 매우 친숙하지 않으며 호출자가 이미 액세스 할 수 있기 때문입니다.
마찬가지로 비 가상적 equals이거나 hashCode주소 만 사용하여 객체를 비교할 수 있습니다. 이는 Java와 달리 C ++에서 객체가 자주 복사되므로 객체의 "정체성"을 구분하는 것조차별로 중요하지 않습니다. 항상 의미 있거나 유용합니다. (예를 들어 int실제로는 값 이외의 정체성을 가져서는 안됩니다 ... 같은 값의 두 정수는 같아야합니다.)


Mike Nakis가 지적한이 문제와 취약한 기본 클래스 문제와 관련하여 기본적으로 모든 메소드를 내부적으로 (같은 클래스에서 호출 할 때) 가상적이지 만 가상 행동을 유지 함으로써 Java로 수정하는 흥미로운 연구 / 제안 에 주목하십시오. 외부 적으로 부름; 기존 / 표준 행동 (예 : 모든 곳에서 가상)을 얻기 위해 제안서에 새로운 키워드가 도입되었습니다 . 그래도 몇 장의 논문을 넘어서지 않았다고 생각합니다. open
Fizz

이 논문에 대한 좀 더 자세한 논의는 lambda-the-ultimate.org/classic/message12271.html
Fizz

공통 기본 클래스를 갖는 것이 가능 테스트 할 것 중 하나를 shared_ptr<Foo> 그것은 또한 있는지 확인하기 위해 shared_ptr<Bar>(다른 포인터 타입 또는 마찬가지로) 경우에도, Foo그리고 Bar서로에 대해 아무것도 몰라 관련이없는 클래스이다. 그러한 것들이 어떻게 사용되는지에 대한 역사를 고려할 때 "원시 포인터"와 함께 작동하는 것은 비용이 많이 들지만, 어쨌든 힙 저장 될 것이라면 추가 비용은 최소화 될 것입니다.
supercat

모든 것에 대한 공통 기본 클래스를 갖는 것이 도움이되지는 않지만, 공통 기본 클래스가 도움이 될 다소 큰 범주의 객체가 있다고 생각합니다. 예를 들어, Java의 많은 (대부분은 아니지만 다수) 클래스는 두 가지 방식으로 사용될 수 있습니다 : 변경 불가능한 데이터의 공유되지 않은 홀더 또는 아무도 수정할 수없는 공유 가능한 데이터 홀더입니다. 두 가지 사용 패턴 모두에서 관리되는 포인터 (참조)는 기본 데이터의 프록시로 사용됩니다. 그러한 모든 데이터에 대해 공통 관리 포인터 유형을 가질 수 있으면 도움이됩니다.
supercat

16

하나의 루트 객체를 가지면 많은 보상없이 수행 할 수있는 작업과 컴파일러가 수행 할 수있는 작업이 제한됩니다.

공통 루트 클래스를 사용하면 모든 컨테이너를 작성하고로 포함 된 것을 추출 할 수 dynamic_cast있지만 컨테이너가 필요한 경우 공통 루트 클래스 없이도 비슷한 것을 boost::any수행 할 수 있습니다 . 그리고 또한 프리미티브를 지원합니다 - 심지어 작은 버퍼 최적화를 지원하고 자바 용어로 거의 "박스 없음"을 남길 수 있습니다.boost::any

C ++는 가치 유형을 지원하고 번성합니다. 리터럴과 프로그래머가 작성한 값 유형. C ++ 컨테이너는 값 유형을 효율적으로 저장, 정렬, 해시, 소비 및 생성합니다.

상속, 특히 모 놀리 식 상속 Java 스타일 기본 클래스는 프리 스토어 기반의 "포인터"또는 "참조"유형을 필요로합니다. 핸들 / 포인터 / 데이터 참조는 클래스의 인터페이스에 대한 포인터를 보유하며 다형성으로 다른 것을 나타낼 수 있습니다.

일부 상황에서는 유용하지만 "공통 기본 클래스"를 사용하여 패턴과 결혼 한 후에는 유용하지 않은 경우에도 전체 패턴베이스를이 패턴의 비용과 수하물에 고정했습니다.

호출 사이트 나 코드를 사용하는 코드에서 "객체"라는 것보다 유형에 대해 더 많이 알고있을 것입니다.

함수가 단순하면 함수를 템플리트로 작성하면 호출 사이트의 정보가 버려지지 않는 오리 유형 컴파일 시간 기반 다형성이 제공됩니다. 기능이 더 복잡한 경우 수행하려는 유형 (예 : 직렬화 및 역 직렬화)에 대한 균일 한 작업을 빌드하고 저장 (컴파일 시간에)하여 런타임에 소비 할 수 있도록 유형 삭제를 수행 할 수 있습니다. 다른 번역 단위의 코드.

모든 것을 직렬화 할 수있는 라이브러리가 있다고 가정하십시오. 한 가지 방법은 기본 클래스를 사용하는 것입니다.

struct serialization_friendly {
  virtual void write_to( my_buffer* ) const = 0;
  virtual void read_from( my_buffer const* ) = 0;
  virtual ~serialization_friendly() {}
};

이제 여러분이 작성하는 모든 코드가 가능합니다 serialization_friendly.

void serialize( my_buffer* b, serialization_friendly const* x ) {
  if (x) x->write_to(b);
}

를 제외한 std::vector모든 컨테이너를 작성해야합니다. 그리고 그 큰 숫자 라이브러리에서 얻은 정수는 아닙니다. 그리고 그런 유형은 직렬화가 필요하지 않다고 썼습니다. 그리고이 아니 tuple거나 int또는 double, 나 std::ptrdiff_t.

우리는 다른 접근 방식을 취합니다.

void write_to( my_buffer* b, int x ) {
  b->write_integer(x);
}    
template<class T,
  class=std::enable_if_t< void_t<
    std::declval<T const*>()->write_to( std::declval<my_buffer*>()
  > >
>
void write_to( my_buffer* b, T const* x ) {
  if (x) x->write_to(b);
}
template<class T>
void serialize( my_buffer* b, T const& t ) {
  write_to( b, t );
}

겉보기에는 아무것도하지 않는 것으로 구성되어 있습니다. 이제는 유형의 네임 스페이스 또는 유형의 메서드에서 자유 함수로 write_to재정 의하여 확장 할 수 있습니다 write_to.

우리는 약간의 삭제 코드를 작성할 수도 있습니다.

namespace details {
  struct can_serialize_pimpl {
    virtual void write_to( my_buffer* ) const = 0;
    virtual void read_from( my_buffer const* ) = 0;
    virtual ~can_serialize_pimpl() {}
  };
}
struct can_serialize {
  void write_to( my_buffer* b ) const { pImpl->write_to(b); }
  void read_from( my_buffer const* b ) { pImpl->read_from(b); }
  std::unique_ptr<details::can_serialize_pimpl> pImpl;
  template<class T> can_serialize(T&&);
};
namespace details { 
  template<class T>
  struct can_serialize : can_serialize_pimpl {
    std::decay_t<T>* t;
    void write_to( my_buffer*b ) const final override {
      serialize( b, std::forward<T>(*t) );
    }
    void read_from( my_buffer const* ) final override {
      deserialize( b, std::forward<T>(*t) );
    }
    can_serialize(T&& in):t(&in) {}
  };
}
template<class T> can_serialize::can_serialize<T>(T&&t):pImpl(
  std::make_unique<details::can_serialize<T>>( std::forward<T>(t) );
) {}

이제 임의의 유형을 가져 와서 가상 인터페이스를 통해 나중에 can_serialize호출 할 수 있는 인터페이스에 자동 상자를 넣을 수 있습니다 serialize.

그래서:

void writer_thingy( can_serialize s );

대신 직렬화 할 수있는 모든 것을 취하는 함수입니다.

void writer_thingy( serialization_friendly const* s );

그리고 첫 번째, 두 번째는 달리, 그것은 처리 할 수있는 int, std::vector<std::vector<Bob>>자동으로.

작성하는 데 많은 시간이 걸리지 않았으며, 특히 이런 종류의 일은 당신이 거의 원하지 않는 일이기 때문에 기본 유형을 요구하지 않고 직렬화 가능한 것으로 취급 할 수있는 능력을 얻었습니다.

무엇을 더, 지금 우리가 할 수 std::vector<T>단순히 대체하여 일류 시민으로 직렬화 write_to( my_buffer*, std::vector<T> const& )- 그 과부하, 그것은에 전달 될 수 can_serialize와의 serializabilty는 std::vector의 vtable에 저장하고 액세스됩니다 .write_to.

간단히 말해 C ++은 필요할 때 강제 상속 계층 구조의 가격을 지불하지 않고도 필요할 때 즉시 단일 기본 클래스의 이점을 구현할 수있을 정도로 강력합니다. 그리고 단일 염기 (가짜 또는 불필요)가 필요한 시간은 합리적입니다.

유형이 실제로 자신의 정체성이고 그 유형을 알면 최적화 기회가 많이 있습니다. 데이터는 로컬에 연속적으로 저장되어 있으며 (현대 프로세서의 캐시 친화성에 매우 중요), 컴파일러는 불투명 한 가상 메소드 포인터를 사용하지 않고 지정된 작업이 수행하는 작업을 쉽게 이해할 수 있습니다. 다른 쪽)을 사용하면 명령을 최적으로 재정렬하고 둥근 구멍에 적은 수의 둥근 못을 박을 수 있습니다.


8

위의 많은 좋은 답변이 있으며 @ratchetfreak의 답변에 표시된 것처럼 다른 방법으로 기본 객체로 수행하는 모든 작업을 더 잘 수행 할 수 있다는 명확한 사실이 있지만 그에 대한 의견은 매우 중요하지만 상속 다이아몬드를 만들지 않는 또 다른 이유가 있습니다.다중 상속이 사용될 때. 범용 기본 클래스에서 기능이있는 경우 다중 상속 사용을 시작하자마자 액세스 체인의 변형에 따라 상속 체인의 경로에 따라 다르게 오버로드 될 수있는 변형을 지정해야합니다. 또한 비효율적 일 수 있기 때문에베이스는 가상이 될 수 없습니다 (메모리 사용 및 로컬 리티에서 잠재적으로 막대한 비용으로 모든 오브젝트에 가상 테이블이 있어야 함). 이것은 매우 빠른 물류 악몽이 될 것입니다.


1
다이아몬드 문제에 대한 한 가지 해결책은 다중 경로를 통해 기본 유형을 비 가상적으로 파생하는 모든 유형이 해당 기본 유형의 모든 가상 구성원을 대체하는 것입니다. 공통 기본 유형이 시작부터 언어에 내장 된 경우 컴파일러는 합법적 인 (필수는 아니지만) 기본 구현을 자동 생성 할 수 있습니다.
supercat

5

사실 Microsoft의 초기 C ++ 컴파일러 및 라이브러리 (Visual C ++, 16 비트에 대해 알고 있음)에는 이와 같은 클래스가 CObject있습니다.

그러나 그 당시 "templates"는이 간단한 C ++ 컴파일러에 의해 지원되지 않았으므로 이와 같은 클래스 std::vector<class T>는 불가능했습니다. 대신 "벡터"구현은 한 가지 유형의 클래스 만 처리 할 수 ​​있으므로 std::vector<CObject>오늘날 과 비슷한 클래스가있었습니다 . CObject거의 모든 클래스의 기본 클래스 이기 때문에 (불행히도 현대 컴파일러 CString와 동일 하지는 않음 string) 거의 모든 종류의 객체를 저장하는 데이 클래스를 사용할 수 있습니다.

최신 컴파일러는 템플릿을 지원하기 때문에 "일반적인 기본 클래스"사용 사례는 더 이상 제공되지 않습니다.

이러한 일반 기본 클래스를 사용하면 생성자 호출과 같은 메모리와 런타임에 약간의 비용이 소요된다는 사실에 대해 생각해야합니다. 따라서 이러한 클래스를 사용할 때는 단점이 있지만 최소한 현대적인 C ++ 컴파일러를 사용할 때는 이러한 클래스에 대한 사용 사례가 거의 없습니다.


3
MFC인가요? [코멘트 패딩]
user253751

3
실제로 MFC입니다. 세상이 어떻게해야하는지 보여주는 OO 디자인의 빛나는 표지. 아, 잠깐만 ...
gbjbaanb

4
@gbjbaanb Turbo Pascal과 Turbo C ++는 TObjectMFC가 존재하기 전에 자체적으로 가지고 있었습니다. 디자인의 그 부분에 대해 Microsoft를 비난하지 마십시오. 당시의 거의 모든 사람들에게 좋은 생각처럼 보였습니다.
hvd

템플릿 이전에도 C ++로 스몰 토크를 쓰려고하면 끔찍한 결과가 나왔습니다.
JDługosz

@hvd 아직도, MFC는 볼랜드가 생산 한 것보다 훨씬 객체 지향 디자인의 예입니다.
Jules

5

Java에서 나온 또 다른 이유를 제안하려고합니다.

때문에 당신 에 대한 기본 클래스 만들 수 없습니다 모든 것을 적어도 보일러 플레이트의 무리없이.

당신은 당신의 자신의 클래스를 위해 그것을 벗어날 수 있습니다-그러나 아마도 당신은 많은 코드를 복제하게 될 것입니다. 예 : " std::vector구현되지 않기 때문에 여기서 사용할 수 없습니다 . 올바른 일을 IObject하는 새로운 파생물 IVectorObject을 만드는 것이 좋습니다 ."

이것은 내장 라이브러리 나 표준 라이브러리 클래스 또는 다른 라이브러리의 클래스를 다룰 때마다 해당됩니다.

이 언어에 내장 된 이제 경우는 같은 것들로 끝날 것 Integerint자바에 혼란, 또는 언어 구문에 큰 변화. (내가 생각하기에 다른 언어는 모든 유형으로 작성하는 데 훌륭한 일을했다고 생각합니다. 루비가 더 좋은 예처럼 보입니다.)

또한 기본 클래스가 런타임 다형성이 아닌 경우 (예 : 가상 함수 사용) 프레임 워크와 같은 특성을 사용하면 동일한 이점을 얻을 수 있습니다.

예를 들어 다음 대신에 .toString()다음을 수행 할 수 있습니다.

template<typename T>
struct ToStringTrait;

template<typename T> 
std::string toString(const T & t) {
  return ToStringTrait<T>::toString(t);
}

template<>
struct ToStringTrait<int> {
  std::string toString(int v) {
    return itoa(v);
  }
}

template<typename T>
struct ToStringTrait<std::vector<T>> {
  std::string toString(const std::vector<T> &v) {
    std::stringstream ss;
    ss<<"{";
    for(int i=0; i<v.size(); ++i) {
      ss<<toString(v[i]);
    }
    ss<<"}";
    return ss.str();
  }
}

3

틀림없이 "공허"는 보편적 기본 계층의 많은 역할을 수행합니다. 당신은 포인터를void* . 그런 다음 해당 포인터를 비교할 수 있습니다. static_cast원래 수업으로 돌아갈 수 있습니다 .

그러나 당신이 무엇을 할 수 와 함께 할 수 void있는 당신이 할 수있는 Object당신이 정말로이 객체의 유형을 파악하기 위해 사용 RTTI입니다. 이것은 궁극적으로 C ++의 모든 객체가 RTTI를 갖지 않는 방법에 따라 달라지며 실제로 너비가 0 인 객체를 가질 수 있습니다.


1
너비가 0 인베이스 클래스 서브 오브젝트 만 정상 오브젝트가 아닙니다.
중복 제거기

@Deduplicator C ++ 17은 업데이트 [[no_unique_address]]를 통해 멤버 하위 객체에 너비를 0으로 지정하기 위해 컴파일러에서 사용할 수있는을 추가합니다 .
underscore_d

1
@underscore_d C ++ 20을 계획 했으므로 [[no_unique_address]]컴파일러가 EBO 멤버 변수를 허용합니다.
중복 제거기

@Dupuplicator 으악, p. 이미 C ++ 17을 사용하기 시작했지만 여전히 실제보다 더 최신이라고 생각합니다!
underscore_d

2

Java는 정의되지 않은 동작이 없어야 한다는 디자인 철학을 채택 합니다 . 다음과 같은 코드 :

Cat felix = GetCat();
Woofer Rover = (Woofer)felix;
Rover.woof();

인터페이스를 구현 felix하는 하위 유형을 보유 하는지 테스트합니다 . 그렇지 않으면 캐스트를 수행하고 호출 하지 않으면 예외가 발생합니다. 코드의 동작은 완전히 여부를 정의 구현 여부 .CatWooferwoof()felixWoofer

C ++은 프로그램이 어떤 조작을 시도하지 않아야한다면, 그 조작이 시도 된 경우 생성 된 코드가 무엇을하든 상관 없으며, 컴퓨터는 "해야 할"행동을 제한하려고 시도하는 데 시간을 낭비해서는 안된다는 철학을 취합니다. 절대 발생하지 않습니다. C ++에서 a에 캐스트하기 *Cat위해 적절한 간접 연산자를 추가하면 *Woofer코드는 캐스트가 합법적 인 경우 정의 된 동작을 생성 하지만 그렇지 않은 경우 정의되지 않은 동작을 생성합니다 .

사물에 대한 공통 기본 유형을 사용하면 해당 기본 유형의 파생 상품 간의 캐스트 유효성을 검사하고 캐스트 캐스트 작업을 수행 할 수 있지만 캐스트 유효성 검사는 합법적이고 나쁜 일이 발생하지 않는다고 가정하는 것보다 비용이 많이 듭니다. C ++ 철학은 그러한 유효성 검사에는 "일반적으로 필요하지 않은 것에 대한 비용 지불"이 필요하다는 것입니다.

C ++와 관련되어 있지만 새로운 언어에는 문제가되지 않는 또 다른 문제는 여러 프로그래머가 각각 공통 기반을 만들면 해당 클래스를 파생하고 해당 공통 기본 클래스의 작업을 수행하는 코드를 작성한다는 것입니다. 이러한 코드는 다른 기본 클래스를 사용한 프로그래머가 개발 한 객체로는 작동하지 않습니다. 새로운 언어가 모든 힙 객체에 공통 헤더 형식이 필요하고 힙 객체가 허용하지 않은 힙 객체를 허용하지 않은 경우 이러한 헤더가있는 힙 객체에 대한 참조가 필요한 메소드는 모든 힙 객체에 대한 참조를 승인합니다. 이제까지 만들 수 있습니다.

개인적으로, 객체를 "X 형으로 변환 할 수 있는가?"라는 일반적인 방법을 사용하는 것이 언어 / 프레임 워크에서 매우 중요한 기능이라고 생각합니다. 그러나 그러한 기능이 처음부터 언어에 내장되어 있지 않다면 어렵습니다. 나중에 추가하십시오. 개인적으로, 나는 그러한 기본 클래스를 처음에 표준 라이브러리에 추가해야한다고 생각합니다. 다형성으로 사용될 모든 객체는 해당 기본 클래스에서 상속해야합니다. 프로그래머가 각각 고유 한 "기본 유형"을 구현하면 서로 다른 사람들의 코드간에 객체를 전달하는 것이 더 어려워 지지만 많은 프로그래머가 상속 한 공통 기본 유형을 사용하면 더 쉬워집니다.

추가

템플릿을 사용하여 "임의의 객체 홀더"를 정의하고 그 안에 포함 된 객체의 유형에 대해 물어볼 수 있습니다. Boost 패키지에는라는 것이 포함되어 있습니다 any. 따라서 C ++에 표준 "유형 검사 가능 참조"유형이 없더라도 작성할 수 있습니다. 이것은 언어 표준에 무언가가 없다는 것, 즉 다른 프로그래머의 구현 사이의 비 호환성으로 인해 발생하는 문제를 해결하지 못하지만 모든 것이 파생되는 기본 유형을 갖지 않고 C ++가 얻는 방법을 설명합니다. 하나처럼 행동하는 것.


이 캐스트는 C ++ , JavaC # 에서 컴파일 타임에 실패합니다 .
milleniumbug

1
@milleniumbug : Woofer인터페이스이고 Cat상속 가능한 경우 캐스트에서 WoofingCat상속 Cat하고 구현할 가능성이 있기 때문에 캐스트는 합법적 Woofer입니다. Java 컴파일 / 링크 모델에서 a를 WoofingCat만들려면 Catnor 에 대한 소스 코드에 액세스 할 필요가 없습니다 Woofer.
supercat

3
C ++에는 dynamic_cast 가 있는데, 이것은 a에서 a Cat로의 캐스트 시도를 올바르게 처리하고 Woofer"X 유형으로 변환 할 수 있습니까?"라는 질문에 대답합니다. C ++을 사용하면 캐스트를 강요 할 수 있습니다. 원인을 알 수 있습니다. 실제로 무엇을하고 있는지 알 수 있지만 실제로 의도 한 것이 아닌 경우 도움이됩니다.
Rob K

2
@RobK : 물론 구문에 대해 맞습니다. mea culpa. 나는 dynamic_cast에 대해 조금 더 읽었으며 현대 C ++은 모든 다형성 객체가 객체의 유형을 식별하는 데 필요한 모든 필드 (일반적으로 vtable)가있는 기본 "다형성 객체"기본 클래스에서 파생 된 것으로 보입니다. 포인터이지만 구현 세부 사항입니다). C ++은 그런 식으로 다형성 클래스를 설명하지 않지만 dynamic_cast, 다형성 객체를 가리키는 경우 포인터를 정의하면 동작이 정의되고 그렇지 않은 경우 정의되지 않은 동작은 의미 적 관점에서 볼 수 있습니다.
supercat

2
... 모든 다형성 객체는 동일한 레이아웃으로 일부 정보를 저장하며 모두 비다 형성 객체가 지원하지 않는 동작을 지원합니다. 내 말에, 그것은 언어 정의가 그러한 용어를 사용하는지 여부에 관계없이 공통의 기초에서 파생되는 것처럼 행동한다는 것을 의미합니다.
supercat 2019

1

Symbian C ++에는 실제로 특정 방식으로 동작하는 모든 객체 (주로 힙을 할당 한 경우)에 대한 범용 기본 클래스 인 CBase가있었습니다. 가상 소멸자를 제공하고 생성시 클래스의 메모리를 0으로 만들고 복사 생성자를 숨겼습니다.

배후의 이론적 근거는 임베디드 시스템을위한 언어였으며 C ++ 컴파일러와 스펙은 10 년 전 정말 엉망이었습니다.

모든 클래스가이 클래스에서 상속 된 것은 아니며 일부 클래스 만 상속됩니다.

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