問題描述
我想問一下 Effective C++ 中提出的一個具體觀點.
I wanted to ask about a specific point made in Effective C++.
它說:
如果一個類需要像一個多態類一樣運行,那么析構函數應該是虛擬的.它還進一步補充說,由于 std::string
沒有虛擬析構函數,因此永遠不應從中派生.另外 std::string
甚至都沒有被設計為基類,忘記多態基類.
A destructor should be made virtual if a class needs to act like a polymorphic class. It further adds that since
std::string
does not have a virtual destructor, one should never derive from it. Alsostd::string
is not even designed to be a base class, forget polymorphic base class.
我不明白要成為基類(不是多態類)的類具體需要什么?
I do not understand what specifically is required in a class to be eligible for being a base class (not a polymorphic one)?
我不應該從 std::string
類派生的唯一原因是它沒有虛擬析構函數嗎?出于可重用性的目的,可以定義一個基類,并且可以從它繼承多個派生類.那么是什么讓 std::string
甚至沒有資格作為基類?
Is the only reason that I should not derive from std::string
class is it does not have a virtual destructor? For reusability purpose a base class can be defined and multiple derived class can inherit from it. So what makes std::string
not even eligible as a base class?
另外,如果有一個純粹為了可重用性目的而定義的基類,并且有許多派生類型,有沒有辦法阻止客戶端執行 Base* p = new Derived()
因為類不是要多態使用嗎?
Also, if there is a base class purely defined for reusability purpose and there are many derived types, is there any way to prevent client from doing Base* p = new Derived()
because the classes are not meant to be used polymorphically?
推薦答案
我認為這個聲明反映了這里的混亂(強調我的):
I think this statement reflects the confusion here (emphasis mine):
我不明白在類中有什么具體要求才有資格成為基類(不是多態類)?
I do not understand what specifically is required in a class to be eligible for being a base clas (not a polymorphic one)?
在慣用的 C++ 中,從類派生有兩種用途:
In idiomatic C++, there are two uses for deriving from a class:
- 私有繼承,用于使用模板的混合和面向方面的編程.
- public 繼承,僅用于多態情況.編輯:好的,我想這也可以用在一些混合場景中——比如
boost::iterator_facade
——當 CRTP 正在使用中.
- private inheritance, used for mixins and aspect oriented programming using templates.
- public inheritance, used for polymorphic situations only. EDIT: Okay, I guess this could be used in a few mixin scenarios too -- such as
boost::iterator_facade
-- which show up when the CRTP is in use.
如果你不想做一些多態的事情,絕對沒有理由在 C++ 中公開派生一個類.該語言帶有免費函數作為該語言的標準特性,而免費函數正是您在這里應該使用的.
There is absolutely no reason to publicly derive a class in C++ if you're not trying to do something polymorphic. The language comes with free functions as a standard feature of the language, and free functions are what you should be using here.
這樣想——你真的想僅僅因為你想添加一些方法就強迫你的代碼的客戶端轉換為使用一些專有的字符串類嗎?因為與 Java 或 C#(或大多數類似的面向對象語言)不同,當您在 C++ 中派生類時,基類的大多數用戶都需要了解這種更改.在Java/C#中,類通常是通過引用來訪問的,類似于C++的指針.因此,涉及到一定程度的間接性,它將您的類的客戶端解耦,允許您在其他客戶端不知道的情況下替換派生類.
Think of it this way -- do you really want to force clients of your code to convert to using some proprietary string class simply because you want to tack on a few methods? Because unlike in Java or C# (or most similar object oriented languages), when you derive a class in C++ most users of the base class need to know about that kind of a change. In Java/C#, classes are usually accessed through references, which are similar to C++'s pointers. Therefore, there's a level of indirection involved which decouples the clients of your class, allowing you to substitute a derived class without other clients knowing.
然而,在 C++ 中,類是值類型——與大多數其他面向對象語言不同.看到這一點的最簡單方法是所謂的切片問題.基本上,請考慮:
However, in C++, classes are value types -- unlike in most other OO languages. The easiest way to see this is what's known as the slicing problem. Basically, consider:
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
的復制構造函數來進行復制,而不是派生對象的復制構造函數 -- 不管 std::string
的子類是什么.這可能會導致您的方法與附加到字符串的任何內容之間出現不一致.函數 StringToNumber
不能簡單地接受任何你的派生對象并復制它,僅僅因為你的派生對象可能具有與 std::string
不同的大小——但是這個函數被編譯為只為自動存儲中的 std::string
保留空間.在 Java 和 C# 中,這不是問題,因為唯一涉及的自動存儲是引用類型,并且引用的大小始終相同.在 C++ 中不是這樣.
If you pass your own string to this method, the copy constructor for std::string
will be called to make a copy, not the copy constructor for your derived object -- no matter what child class of std::string
is passed. This can lead to inconsistency between your methods and anything attached to the string. The function StringToNumber
cannot simply take whatever your derived object is and copy that, simply because your derived object probably has a different size than a std::string
-- but this function was compiled to reserve only the space for a std::string
in automatic storage. In Java and C# this is not a problem because the only thing like automatic storage involved are reference types, and the references are always the same size. Not so in C++.
長話短說——不要使用繼承來添加 C++ 中的方法.這不是慣用的,會導致語言出現問題.盡可能使用非友元、非成員函數,然后是組合.除非您是模板元編程或想要多態行為,否則不要使用繼承.有關詳細信息,請參閱 Scott Meyers 的 Effective C++ 條目23:優先使用非成員非友元函數而不是成員函數.
Long story short -- don't use inheritance to tack on methods in C++. That's not idiomatic and results in problems with the language. Use non-friend, non-member functions where possible, followed by composition. Don't use inheritance unless you're template metaprogramming or want polymorphic behavior. For more information, see Scott Meyers' Effective C++ Item 23: Prefer non-member non-friend functions to member functions.
這是一個更完整的示例,顯示了切片問題.您可以在 codepad.org
Here's a more complete example showing the slicing problem. You can see it's output on 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);
}
這篇關于為什么不應該從 c++ std 字符串類派生?的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!