왜 C ++ 표준 문자열 클래스에서 파생되지 않아야합니까?
효과적인 C ++에서 만든 구체적인 요점에 대해 물어보고 싶었습니다.
그것은 말한다 :
클래스가 다형성 클래스처럼 작동해야하는 경우 소멸자는 가상으로 만들어야합니다. 또한
std::string
가상 소멸자가 없기 때문에 그로부터 파생해서는 안된다고 덧붙입니다 . 또한std::string
기본 클래스로 설계되지 않았으므로 다형성 기본 클래스를 잊어 버립니다.
기본 클래스 (다형성이 아님)가되기 위해 클래스에서 특별히 필요한 것이 무엇인지 이해하지 못합니까?
std::string
클래스 에서 파생되지 않아야하는 유일한 이유 는 가상 소멸자가 없기 때문입니까? 재사용을 위해 기본 클래스를 정의하고 여러 파생 클래스를 상속 할 수 있습니다. 그렇다면 std::string
기본 클래스로 자격 이 없는 이유는 무엇 입니까?
또한 재사용을 위해 순수하게 정의 된 기본 클래스가 있고 파생 유형이 많은 Base* p = new Derived()
경우 클래스가 다형성으로 사용되지 않기 때문에 클라이언트가 수행하지 못하도록 방지 할 수있는 방법이 있습니까?
나는이 진술이 여기에 혼란을 반영한다고 생각한다 (내 강조).
클래스에서 기본 클래스 ( 다형성이 아님) 가되기 위해 특별히 필요한 것이 무엇인지 이해하지 못 합니까?
관용적 C ++에서는 클래스에서 파생하는 데 두 가지 용도가 있습니다.
- 템플릿을 사용하는 믹스 인 및 측면 지향 프로그래밍에 사용되는 개인 상속.
- 다형성 상황에만 사용되는 공용 상속 . 편집 : 좋아, 이것은 CRTP 가 사용
boost::iterator_facade
중일 때 나타나는 몇 가지 믹스 인 시나리오에서도 사용할 수 있다고 생각 합니다.
다형성을 시도하지 않는다면 C ++에서 공개적으로 클래스를 파생시킬 이유가 전혀 없습니다. 언어는 언어의 표준 기능으로 무료 기능과 함께 제공되며 여기에서 사용해야하는 것은 무료 기능입니다.
이런 식으로 생각해보세요. 단순히 몇 가지 메서드를 사용하고 싶어서 코드 클라이언트가 독점적 인 문자열 클래스를 사용하도록 강제로 변환하고 싶습니까? Java 또는 C # (또는 가장 유사한 객체 지향 언어)과 달리 C ++에서 클래스를 파생 할 때 기본 클래스의 대부분의 사용자는 이러한 종류의 변경에 대해 알아야합니다. Java / C #에서 클래스는 일반적으로 C ++의 포인터와 유사한 참조를 통해 액세스됩니다. 따라서 클래스의 클라이언트를 분리하여 다른 클라이언트가 알지 못하는 상태에서 파생 클래스를 대체 할 수있는 간접적 인 수준이 있습니다.
그러나 C ++에서 클래스는 대부분의 다른 OO 언어와 달리 값 유형 입니다. 이를 확인하는 가장 쉬운 방법 은 슬라이싱 문제 로 알려진 것 입니다. 기본적으로 다음을 고려하십시오.
int StringToNumber(std::string copyMeByValue)
{
std::istringstream converter(copyMeByValue);
int result;
if (converter >> result)
{
return result;
}
throw std::logic_error("That is not a number.");
}
이 메서드에 고유 한 문자열을 전달하면의 자식 클래스 가 전달 되더라도 파생 개체의 복사 생성자가 아닌 복사 생성자std::string
가 호출되어 복사본을 만듭니다 . 이로 인해 메서드와 문자열에 첨부 된 모든 항목간에 불일치가 발생할 수 있습니다. 이 함수 는 파생 된 객체가 무엇이든 가져 와서 복사 할 수 없습니다. 단순히 파생 된 객체가 a와 크기가 다를 수 있기 때문입니다. 그러나이 함수는 자동 저장 공간 만 예약하도록 컴파일되었습니다 . Java 및 C #에서는 관련된 자동 저장소와 같은 유일한 것이 참조 유형이고 참조가 항상 동일한 크기이기 때문에 문제가되지 않습니다. C ++에서는 그렇지 않습니다.std::string
StringToNumber
std::string
std::string
간단히 말해서 상속을 사용하여 C ++의 메서드를 사용하지 마십시오. 그것은 관용적이지 않으며 언어에 문제가 있습니다. 가능한 경우 친구가 아닌 비회원 기능을 사용하고 구성이 이어집니다. 템플릿 메타 프로그래밍이 아니거나 다형성 동작을 원하지 않는 한 상속을 사용하지 마십시오. 자세한 내용은 Scott Meyers의 효과적인 C ++ 항목 23 : 멤버 함수보다 멤버가 아닌 비 친구 함수 선호를 참조하십시오 .
편집 : 다음은 슬라이싱 문제를 보여주는 더 완전한 예입니다. codepad.org 에서 출력을 볼 수 있습니다.
#include <ostream>
#include <iomanip>
struct Base
{
int aMemberForASize;
Base() { std::cout << "Constructing a base." << std::endl; }
Base(const Base&) { std::cout << "Copying a base." << std::endl; }
~Base() { std::cout << "Destroying a base." << std::endl; }
};
struct Derived : public Base
{
int aMemberThatMakesMeBiggerThanBase;
Derived() { std::cout << "Constructing a derived." << std::endl; }
Derived(const Derived&) : Base() { std::cout << "Copying a derived." << std::endl; }
~Derived() { std::cout << "Destroying a derived." << std::endl; }
};
int SomeThirdPartyMethod(Base /* SomeBase */)
{
return 42;
}
int main()
{
Derived derivedObject;
{
//Scope to show the copy behavior of copying a derived.
Derived aCopy(derivedObject);
}
SomeThirdPartyMethod(derivedObject);
}
일반적인 조언에 반대하는 측면을 제공하기 위해 (특정 장 황성 / 생산성 문제가 분명하지 않을 때 소리가납니다) ...
합리적인 사용을위한 시나리오
가상 소멸자가없는 기지에서 공개적으로 파생하는 것이 좋은 결정이 될 수있는 시나리오가 하나 이상 있습니다.
- 전용 사용자 정의 유형 (클래스)에서 제공하는 유형 안전성 및 코드 가독성 이점을 원합니다.
- 기존 기반은 데이터를 저장하는 데 이상적이며 클라이언트 코드가 사용하고자하는 저수준 작업을 허용합니다.
- 해당 기본 클래스를 지원하는 함수를 재사용하는 편리함을 원합니다.
- 데이터에 논리적으로 필요한 추가 불변은 파생 된 유형으로 데이터에 명시 적으로 액세스하는 코드에서만 적용 할 수 있으며, 디자인에서 "자연스럽게"발생하는 범위와 클라이언트를 얼마나 신뢰할 수 있는지에 따라 논리적으로 이상적인 불변성을 이해하고 협력하는 코드, 파생 클래스의 멤버 함수가 기대치를 재확인하고 던지기를 원할 수 있습니다.
- 파생 클래스는 사용자 지정 검색, 데이터 필터링 / 수정, 스트리밍, 통계 분석, (대체) 반복기와 같이 데이터에 대해 작동하는 매우 유형별 편의 기능을 추가합니다.
- 클라이언트 코드를 기본에 연결하는 것이 파생 클래스에 연결하는 것보다 더 적절합니다 (기본이 안정적이거나 파생 클래스의 핵심 기능에 대한 개선 사항을 반영하기 때문에).
- 다른 방법으로 말하면, 파생 클래스가 기본 클래스와 동일한 API를 계속 노출하기를 원합니다. 즉, 클라이언트 코드가 강제로 변경 되더라도 기본 및 파생 API를 확장 할 수있는 어떤 방식 으로든 절연하는 것이 아니라 동기화
- 삭제를 담당하는 코드의 일부에서 기본 및 파생 개체에 대한 포인터를 혼합하지 않을 것입니다.
이것은 매우 제한적으로 들릴 수 있지만 실제 프로그램에서이 시나리오와 일치하는 경우가 많이 있습니다.
배경 논의 : 상대적 장점
프로그래밍은 타협에 관한 것입니다. 개념적으로 "올바른"프로그램을 작성하기 전에 :
- 실제 프로그램 논리를 난독 화하는 추가 복잡성과 코드가 필요한지 여부를 고려하여 특정 문제를보다 강력하게 처리 함에도 불구하고 전체적으로 오류가 발생하기 쉽습니다.
- 문제의 가능성과 결과에 대해 실제 비용을 비교합니다.
- "투자 수익"과 시간에 할 수있는 다른 작업을 고려하십시오.
잠재적 인 문제 가 프로그램에서의 접근성, 범위 및 사용 특성에 대한 통찰력을 제공하여 시도하는 사람을 상상할 수없는 개체 의 사용과 관련이 있거나 위험한 사용을위한 컴파일 시간 오류를 생성 할 수 있습니다 (예 : 파생 된 클래스 크기가 기본 크기와 일치하므로 새 데이터 멤버를 추가하지 못함), 그 밖의 다른 항목은 너무 이른 과잉 엔지니어링 일 수 있습니다. 깔끔하고 직관적이며 간결한 디자인과 코드로 쉽게 승리하십시오.
파생을 고려하는 이유 가상 소멸자
B에서 공개적으로 파생 된 클래스 D가 있다고 가정합니다. 노력없이 B에 대한 작업은 D에서 가능합니다 (생성자가 많지만 생성자가 많더라도 템플릿 하나만 있으면 효과적인 전달을 제공 할 수 있습니다. 각각의 고유 한 수의 생성자 인수 : 예 template <typename T1, typename T2> D(const T1& x1, const T2& t2) : B(t1, t2) { }
. C ++ 0x 가변 템플릿에서 더 나은 일반화 된 솔루션.)
또한 B가 변경되면 기본적으로 D는 해당 변경 사항을 노출하여 동기화 상태를 유지하지만 누군가 D에 도입 된 확장 기능을 검토하여 유효한지 여부 와 클라이언트 사용 을 확인해야 할 수 있습니다 .
다시 말하면 기본 클래스와 파생 클래스 간의 명시 적 결합 이 감소했지만 기본과 클라이언트 간의 결합이 증가했습니다 .
이것은 종종 원하는 것이 아니지만 때로는 이상적이며 다른 경우에는 문제가되지 않습니다 (다음 단락 참조). 베이스를 변경하면 코드베이스 전체에 분산 된 위치에서 더 많은 클라이언트 코드가 변경되고, 때로는베이스를 변경하는 사람들이 그에 따라 검토하거나 업데이트하기 위해 클라이언트 코드에 액세스하지 못할 수도 있습니다. 하지만 때로는 더 낫습니다. 파생 클래스 공급자 ( "중간자") 인 경우 기본 클래스 변경 사항이 클라이언트에 전달되기를 원하고 일반적으로 클라이언트가 클라이언트가 코드를 업데이트 할 수 있도록 (때로는 강제로) 지속적으로 참여할 필요없이 기본 클래스가 변경되면 공개 파생이 이상적 일 수 있습니다. 이것은 당신의 클래스가 그 자체로 독립적 인 엔티티가 아니라 기본에 대한 부가가치가 적은 경우에 일반적입니다.
다른 경우에는 기본 클래스 인터페이스가 너무 안정적이므로 결합이 문제가되지 않는 것으로 간주 될 수 있습니다. 이것은 특히 표준 컨테이너와 같은 클래스에 해당됩니다.
요약하자면, 공개 파생은 파생 클래스에 대한 이상적이고 친숙한 기본 클래스 인터페이스를 가져 오거나 근사화하는 빠른 방법입니다. 멤버 함수로 사용할 수있는 추가 기능과 함께 간결하고 명확하게 유지 관리자와 클라이언트 코더 모두에게 올바른 방식으로 Sutter, Alexandrescu 등과 분명히 다른 IMHO는 사용성, 가독성을 지원하고 IDE를 포함한 생산성 향상 도구를 지원할 수 있습니다.)
C ++ 코딩 표준-Sutter 및 Alexandrescu-단점 검사
C ++ 코딩 표준 의 항목 35 에는 std::string
. 시나리오가 진행됨에 따라 크고 유용한 API를 노출해야하는 부담을 보여주는 것이 좋지만 기본 API가 표준 라이브러리의 일부이기 때문에 매우 안정적이므로 좋고 나쁩니다. 안정적인 염기는 일반적인 상황이지만 변동성이 높은 상황보다 더 일반적이지 않으며 좋은 분석은 두 경우 모두와 관련되어야합니다. 책의 문제 목록을 고려하면서 다음과 같은 경우에 문제의 적용 가능성을 구체적으로 대조합니다.
a) class Issue_Id : public std::string { ...handy stuff... };
<-공개 파생, 논란의 여지가있는 사용
b) class Issue_Id : public string_with_virtual_destructor { ...handy stuff... };
<-더 안전한 OO 파생
c) class Issue_Id { public: ...handy stuff... private: std::string id_; };
<-구성 접근
d) std::string
독립적 인 지원 기능을 사용하여 어디서나 사용
(캡슐화, 유형 안전성 및 잠재적으로 풍부한 API를 제공하므로 구성이 허용되는 관행에 동의 할 수 있기를 바랍니다 std::string
.)
따라서 새로운 코드를 작성하고 OO의 의미에서 개념적 엔티티에 대해 생각하기 시작한다고 가정 해 보겠습니다. 버그 추적 시스템 (JIRA를 생각하고 있습니다)에서 그중 하나가 Issue_Id라고 할 수 있습니다. 데이터 내용은 텍스트 형식입니다. 알파벳 프로젝트 ID, 하이픈 및 증가하는 발행 번호 (예 : "MYAPP-1234")로 구성됩니다. 이슈 ID는에 저장 될 수 있으며 이슈 ID에 std::string
필요한 텍스트 검색 및 조작 작업이 많을 것입니다. 이미 제공된 것의 많은 부분 집합과 std::string
좋은 측정을 위해 몇 개 더 있습니다 (예 : 프로젝트 ID 구성 요소 가져 오기, 제공 다음 가능한 문제 ID (MYAPP-1235)).
Sutter와 Alexandrescu의 문제 목록에 대해 ...
비 멤버 함수는 이미
string
s를 조작하는 기존 코드 내에서 잘 작동합니다 . 대신를 제공하면super_string
코드베이스를 통해 강제로 변경하여 유형 및 함수 서명을super_string
.
이 주장 (및 아래의 대부분)의 근본적인 실수는 유형 안전성의 이점을 무시하고 몇 가지 유형 만 사용하는 편리함을 촉진한다는 것입니다. a)의 대안으로 c) 또는 b)에 대한 통찰력보다는 위의 d)에 대한 선호도를 표현하고 있습니다. 프로그래밍 기술은 합리적인 재사용, 성능, 편의성 및 안전성을 달성하기 위해 고유 한 유형의 장단점을 균형있게 조정하는 것을 포함합니다. 아래 단락에서 이에 대해 자세히 설명합니다.
공개 파생을 사용하여 기존 코드는 기본 클래스에 string
으로 암시 적으로 액세스 할 수 string
있으며 항상 그랬던 것처럼 계속 작동합니다. 기존 코드가 super_string
(이 경우 Issue_Id)의 추가 기능을 사용하기를 원할 것이라고 생각할 특별한 이유가 없습니다 . 실제로 super_string
, 및을 (를) 만드는 애플리케이션에 이미 존재하는 하위 수준 지원 코드 인 경우가 많습니다. 따라서 확장 된 기능에 의해 제공되는 요구를 인식하지 못합니다. 예를 들어 멤버가 아닌 함수 to_upper(std::string&, std::string::size_type from, std::string::size_type to)
가 있다고 가정합니다 Issue_Id
. 여전히 .
따라서 비회원 지원 기능이 새로운 코드와 긴밀하게 연결되는 고의적 인 비용으로 정리되거나 확장되지 않는 한 건드릴 필요가 없습니다. 이 경우 입니다 (위의 경우에만 알파 문자를 선도하는 데이터 내용 형식에 대한 통찰력을 사용하여, 예를 들어) 지원 문제 ID에 철저하게되고, 그때는 정말 통과 될 수 있도록 아마 좋은 일을의 Issue_Id
과부하 람을 만들어 to_upper(Issue_Id&)
유형 안전성을 허용하는 파생 또는 구성 접근법을 고수합니다. super_string
또는 구성이 사용 되는지 여부 는 노력이나 유지 관리에 차이가 없습니다. to_upper_leading_alpha_only(std::string&)
재사용 독립 지원 기능은 가능성이 많이 사용되는 것은 아니다 - 나는 이러한 기능을 원하는 마지막 시간을 기억하지 수 있습니다.
std::string
어디에서나 사용하려는 충동 은 모든 인수를 변형 또는 void*
s의 컨테이너로 받아들이는 것과 질적으로 다르지 않으므로 임의의 데이터를 받아들이 기 위해 인터페이스를 변경할 필요가 없지만 오류가 발생하기 쉬운 구현과 덜 자체 문서화 및 컴파일러 -확인 가능한 코드.
문자열을받는 인터페이스 함수는 이제 다음을 수행해야합니다. a)
super_string
의 추가 된 기능 에서 멀리 떨어져 있어야 합니다 (유용하지 않음); b) 그들의 인수를 super_string에 복사합니다. 또는 c) 문자열 참조를 super_string 참조로 캐스팅합니다 (불쾌하고 잠재적으로 불법).
이것은 첫 번째 요점-새로운 기능을 사용하기 위해 리팩토링해야하는 이전 코드를 다시 방문하는 것으로 보입니다. 이번에는 지원 코드가 아닌 클라이언트 코드입니다. 함수가로 인수 치료를 시작하고자하는 경우 개체 , 새로운 작업 관련이있는 그것은 해야 하는 형식으로 인수를 가지고 시작하고 클라이언트를 생성하고 그 유형을 사용하여 받아 들여야한다. 작곡에도 똑같은 문제가 존재합니다. 그렇지 않으면 c)
추악하지만 아래에 나열된 지침을 따르면 실용적이고 안전 할 수 있습니다.
super_string의 멤버 함수는 비 멤버 함수보다 문자열의 내부에 더 이상 액세스 할 수 없습니다. string에는 보호 된 멤버가 없을 가능성이 있기 때문입니다 (처음부터 파생되지 않았 음을 기억하십시오).
사실이지만 때로는 좋은 일입니다. 많은 기본 클래스에는 보호 된 데이터가 없습니다. 공개 string
인터페이스는 내용을 조작하는 데 필요한 전부이며 유용한 기능 (예 : get_project_id()
위에서 가정)을 이러한 작업 측면에서 우아하게 표현할 수 있습니다. 개념적으로 저는 표준 컨테이너에서 파생 된 여러 번, 기존 라인을 따라 기능을 확장하거나 사용자 정의하지 않고 싶었습니다. 이미 "완벽한"컨테이너입니다. 오히려 특정 동작의 다른 차원을 추가하고 싶었습니다. 내 응용 프로그램에 대한 개인 액세스가 필요하지 않습니다. 이미 좋은 용기이기 때문에 재사용하기에 좋습니다.
경우
super_string
가죽의 일부string
의 기능 (및 파생 클래스에서 비가 상 함수를 다시 정의는, 그냥 숨기고 오버라이드 (override)되지 않음), 그 조작하는 것으로 코드에서 광범위한 혼란이 발생할 수string
의 자신의 인생을 시작했다 그에서 자동으로 변환super_string
들.
컴포지션에서도 마찬가지입니다. 코드가 기본적으로 사물을 통과하여 동기화 상태를 유지하지 않기 때문에 발생할 가능성이 더 높으며 런타임 다형성 계층이있는 일부 상황에서도 마찬가지입니다. 클래스에서 다르게 동작하는 Samed 명명 된 함수는 처음에는 상호 교환이 가능합니다. 이것은 올바른 OO 프로그래밍에 대한 일반적인주의 사항이며 형식 안전성 등의 이점을 포기할 충분한 이유가 아닙니다.
더 많은 상태 를 추가
super_string
하기 위해 상속하려는 경우 [슬라이싱 설명]string
동의-좋은 상황이 아니며, 삭제 문제를 이론 영역에서 매우 실용적인 기준으로 이동시키는 포인터를 통해 삭제 문제를 종종 이동시키기 때문에 개인적으로 선을 그리는 경향이 있습니다. 추가 구성원에 대해 소멸자가 호출되지 않습니다. 그럼에도 불구하고 슬라이싱은 super_string
상속 된 기능을 변경하지 않고 응용 프로그램 별 기능의 또 다른 "차원"을 추가 하는 접근 방식을 고려할 때 원하는 작업을 수행 할 수 있습니다 ....
물론 유지하려는 멤버 함수에 대한 패스 스루 함수를 작성해야하는 것은 지루한 일이지만 이러한 구현은 공개 또는 비공개 상속을 사용하는 것보다 훨씬 낫고 안전합니다.
음, 지루함에 대해 확실히 동의합니다 ....
Guidelines for successful derivation sans virtual destructor
- ideally, avoid adding data members in derived class: variants of slicing can accidentally remove data members, corrupt them, fail to initialise them...
- even more so - avoid non-POD data members: deletion via base-class pointer is technically undefined behaviour anyway, but with non-POD types failing to run their destructors is more likely to have non-theoretical problems with resource leaks, bad reference counts etc.
- honour the Liskov Substitution Principal / you can't robustly maintain new invariants
- for example, in deriving from
std::string
you can't intercept a few functions and expect your objects to remain uppercase: any code that accesses them via astd::string&
or...*
can usestd::string
's original function implementations to change the value) - derive to model a higher level entity in your application, to extend the inherited functionality with some functionality that uses but doesn't conflict with the base; do not expect or try to change the basic operations - and access to those operations - granted by the base type
- for example, in deriving from
- be aware of the coupling: base class can't be removed without affecting client code even if the base class evolves to have inappropriate functionality, i.e. your derived class's usability depends on the ongoing appropriateness of the base
- sometimes even if you use composition you'll need to expose the data member due to performance, thread safety issues or lack of value semantics - so the loss of encapsulation from public derivation isn't tangibly worse
- the more likely people using the potentially-derived class will be unaware of its implementation compromises, the less you can afford to make them dangerous
- therefore, low-level widely deployed libraries with many ad-hoc casual users should be more wary of dangerous derivation than localised use by programmers routinely using the functionality at application level and/or in "private" implementation / libraries
Summary
Such derivation is not without issues so don't consider it unless the end result justifies the means. That said, I flatly reject any claim that this can't be used safely and appropriately in particular cases - it's just a matter of where to draw the line.
Personal experience
I do sometimes derive from std::map<>
, std::vector<>
, std::string
etc - I've never been burnt by the slicing or delete-via-base-class-pointer issues, and I've saved a lot of time and energy for more important things. I don't store such objects in heterogeneous polymorphic containers. But, you need to consider whether all the programmers using the object are aware of the issues and likely to program accordingly. I personally like to write my code to use heap and run-time polymorphism only when needed, while some people (due to Java backgrounds, their prefered approach to managing recompilation dependencies or switching between runtime behaviours, testing facilities etc.) use them habitually and therefore need to be more concerned about safe operations via base class pointers.
Not only is the destructor not virtual, std::string contains no virtual functions at all, and no protected members. That makes it very hard for the derived class to modify its functionality.
Then why would you derive from it?
Another problem with being non-polymorphic is that if you pass your derived class to a function expecting a string parameter, your extra functionality will just be sliced off and the object will be seen as a plain string again.
If you really want to derive from it (not discussing why you want to do it) I think you can prevent Derived
class direct heap instantiation by making it's operator new
private:
class StringDerived : public std::string {
//...
private:
static void* operator new(size_t size);
static void operator delete(void *ptr);
};
But this way you restrict yourself from any dynamic StringDerived
objects.
Why should one not derive from c++ std string class?
Because it is not necessary. If you want to use DerivedString
for functionality extension; I don't see any problem in deriving std::string
. The only thing is, you should not interact between both classes (i.e. don't use string
as a receiver for DerivedString
).
Is there any way to prevent client from doing
Base* p = new Derived()
Yes. Make sure that you provide inline
wrappers around Base
methods inside Derived
class. e.g.
class Derived : protected Base { // 'protected' to avoid Base* p = new Derived
const char* c_str () const { return Base::c_str(); }
//...
};
There are two simple reasons for not deriving from a non-polymorphic class:
- Technical: it introduces slicing bugs (because in C++ we pass by value unless otherwise specified)
- Functional: if it is non-polymorphic, you can achieve the same effect with composition and some function forwarding
If you wish to add new functionalities to std::string
, then first consider using free functions (possibly templates), like the Boost String Algorithm library does.
If you wish to add new data members, then properly wrap the class access by embedding it (Composition) inside a class of your own design.
EDIT:
@Tony noticed rightly that the Functional reason I cited was probably meaningless to most people. There is a simple rule of thumb, in good design, that says that when you can pick a solution among several, you should consider the one with the weaker coupling. Composition has weaker coupling that Inheritance, and thus should be preferred, when possible.
Also, composition gives you the opportunity to nicely wrap the original's class method. This is not possible if you pick inheritance (public) and the methods are not virtual (which is the case here).
The C++ standard states that If Base class destructor is not virtual and you delete an object of Base class that points to the object of an derived class then it causes an undefined Behavior.
C++ standard section 5.3.5/3:
if the static type of the operand is different from its dynamic type, the static type shall be a base class of the operand’s dynamic type and the static type shall have a virtual destructor or the behavior is undefined.
To be clear on the Non-polymorphic class & need of virtual destructor
The purpose of making a destructor virtual is to facilitate the polymorphic deletion of objects through delete-expression. If there is no polymorphic deletion of objects, then you don't need virtual destructor's.
Why not to derive from String Class?
One should generally avoid deriving from any standard container class because of the very reason that they don' have virtual destructors, which make it impossible to delete objects polymorphically.
As for the string class, the string class doesn't have any virtual functions so there is nothing that you can possibly override. The best you can do is hide something.
If at all you want to have a string like functionality you should write a class of your own rather than inherit from std::string.
As soon as you add any member (variable) into your derived std::string class, will you systematically screw the stack if you attempt to use the std goodies with an instance of your derived std::string class? Because the stdc++ functions/members have their stack pointers[indexes] fixed [and adjusted] to the size/boundary of the (base std::string) instance size.
Right?
Please, correct me if I am wrong.
참고URL : https://stackoverflow.com/questions/6006860/why-should-one-not-derive-from-c-std-string-class
'Development Tip' 카테고리의 다른 글
중요한 매개 변수 / 종속성이 누락 된 경우 어떤 예외가 발생합니까? (0) | 2020.12.04 |
---|---|
메소드 정의에서 메소드 이름 뒤에 올 때 등호 ( '=') 기호는 무엇을합니까? (0) | 2020.12.04 |
UTF8을 사용한 MySQL 명령 줄 형식 (0) | 2020.12.04 |
MySQL VARCHAR 크기? (0) | 2020.12.04 |
함수 이름 앞에 별표가있는 기능은 무엇입니까? (0) | 2020.12.04 |