在previous question I quoted Stroustrup中,为什么所有类的公共Object类在c ++中都有问题。在该引文中有声明:
使用通用基类意味着 cost:对象必须是堆分配的 是多态的;
我真的没有看过两次,因为on Bjarnes home page我认为很多眼睛扫过那句话并报告任何错误陈述。
一位意见提供者指出,情况可能并非如此,回想起来,我找不到任何有理由说明这一点的理由。一个简短的测试用例产生了“VDerived :: f()”的预期结果。
struct VBase
{
virtual void f() { cout<<"VBase::f()"<<endl; }
};
struct VDerived:VBase
{
void f() { cout<<"VDerived::f()"<<endl; }
};
void test(VBase& obj){
obj.f();
}
int main(int argc, char** argv) {
VDerived obj;
test(obj);
return 0;
}
当然,如果测试的正式参数是test(VBase obj)
,那么情况将完全不同,但这不是堆栈与堆参数,而是复制语义。
Bjarne是错的还是我在这里错过了什么?
答案 0 :(得分:12)
看起来像我的多态。
当你有间接时,C ++中的多态性有效;也就是说,pointer-to-T
或reference-to-T
。存储T
的地方完全不相关。
Bjarne也犯了“堆分配”的错误,这在技术上是不准确的。
(注意:这并不意味着通用基类是“好”的!)
答案 1 :(得分:6)
我认为Bjarne意味着obj
,或者更确切地说是它指向的对象,在这段代码中不能轻易地基于堆栈:
int f(int arg)
{
std::unique_ptr<Base> obj;
switch (arg)
{
case 1: obj = std::make_unique<Derived1 >(); break;
case 2: obj = std::make_unique<Derived2 >(); break;
default: obj = std::make_unique<DerivedDefault>(); break;
}
return obj->GetValue();
}
你不能在堆栈上有一个更改其类的对象,或者最初不确定它属于哪个类。
(当然,真的很迂腐,人们可以通过在alloca
分配的空间上使用placement-new来分配堆栈中的对象。事实上,复杂的变通办法就在这一点上尽管如此。)
以下代码也无法正常工作:
int f(int arg)
{
Base obj = DerivedFactory(arg); // copy (return by value)
return obj.GetValue();
}
此代码包含对象切片错误:obj
的堆栈空间仅与类Base
的实例一样大;当DerivedFactory
返回具有一些其他成员的派生类的对象时,它们将不会被复制到obj
中,这会使obj
无效并且无法用作派生对象(甚至可能无法使用作为基础对象。)
总结一下,有一类多态行为无法以任何直接的方式使用堆栈对象实现。
当然,任何完全构造的派生对象,无论它存储在何处,都可以充当基础对象,因此可以多态地行动。这只是继承类的对象与其基类之间的 is-a 关系。
答案 2 :(得分:2)
阅读之后我认为重点是(特别是关于复制语义的第二句话)通用基类对于由值处理的对象是无用的,因此它自然会导致更多的处理通过引用因此更多的内存分配开销(想想模板向量与指针向量)。
所以我认为他的意思是对象必须与包含它们的任何结构分开分配,并且它会在堆上导致更多的分配。如上所述,该陈述确实是错误的。
PS(广告长颈鹿队长的评论):有功能
确实没用f(object o)
这意味着泛型函数必须是
f(object &o)
这意味着对象必须是多态的,这反过来意味着它必须单独分配,而经常在堆上意味着,尽管可以在堆栈上。另一方面,你现在有:
template <typename T>
f(T o) // see, no reference
在大多数情况下最终更多效率更高。特别是集合的情况,如果你拥有的只是这些基础对象的向量(就像Java那样),你必须分别分配所有对象。这将是一个很大的开销,特别是考虑到C ++创建时分配器性能不佳(Java仍然有优势,因为复制垃圾收集器更有效,C ++不能使用)。
答案 3 :(得分:2)
Bjarne的陈述不正确。
对象(即类的实例)通过向其类声明添加至少一个虚方法而变得具有潜在的多态性。虚方法添加一个间接级别,允许将调用重定向到调用者可能不知道的实际实现。
对于此,无论实例是堆分配还是堆栈分配,只要通过引用或指针(T& instance
或T* instance
)访问它。
这个一般断言滑到Bjarne的网页上的一个可能原因可能是,堆积分配具有多态行为的实例是极其常见的。这主要是因为通过某种工厂函数获得它的调用者确实不知道实际的实现。
答案 4 :(得分:1)
我认为他不能将其存储在基本类型的变量中。你说的是你可以将它存储在堆栈中,如果它是派生类型的话,那是正确的,因为没有什么特别之处;从概念上讲,它只是存储类的数据,它的衍生物+一个vtable。
编辑:好的,现在我很困惑,重新看一下这个例子。看起来你现在可能就是......
答案 5 :(得分:1)
我认为重点是这不是“真正的”多态的(不管是什么意思: - )。
您可以像这样编写测试函数
template<class T>
void test(T& obj)
{
obj.f();
}
并且它仍然有效,无论这些类是否具有虚函数。
答案 6 :(得分:0)
这是一个很老的问题,已经有很多好的答案。当然,大多数答案都正确地表明,无需分配堆即可实现多态。一些答案试图解释在大多数相关用法中,多态需要堆分配。 然而,似乎需要一个在没有堆分配的情况下实现多态性的可行用法的示例(即,不仅仅是显示它完全可能的语法示例)。
这是一个简单的策略模式示例,使用多态而不分配堆:
策略层次结构
class StrategyBase {
public:
virtual ~StrategyBase() {}
virtual void doSomething() const = 0;
};
class Strategy1 : public StrategyBase {
public:
void doSomething() const override { std::cout << "Strategy1" << std::endl; }
};
class Strategy2 : public StrategyBase {
public:
void doSomething() const override { std::cout << "Strategy2" << std::endl; }
};
非多态类型,持有内部多态策略
class A {
const StrategyBase* strategy;
public:
// just for the example, could be implemented in other ways
const static Strategy1 Strategy_1;
const static Strategy2 Strategy_2;
A(const StrategyBase& s): strategy(&s) {}
void doSomething() const { strategy->doSomething(); }
};
const Strategy1 A::Strategy_1 {};
const Strategy2 A::Strategy_2 {};
使用示例
int main() {
// vector of non-polymorphic types, holding inner polymorphic strategy
std::vector<A> vec { A::Strategy_1, A::Strategy_2 };
// may also add strategy created on stack
// using unnamed struct just for the example
struct : StrategyBase {
void doSomething() const override {
std::cout << "Strategy3" << std::endl;
}
} strategy3;
vec.push_back(strategy3);
for(auto a: vec) {
a.doSomething();
}
}
输出:
Strategy1
Strategy2
Strategy3
答案 7 :(得分:-1)
假设我们有 2 个类
class Base
{
public:
int x = 1;
};
class Derived
: public Base
{
public:
int y = 5;
};
int main()
{
Base o = Derived{ 50, 50 };
std::cout << Derived{ o }.y;
return 0;
}
输出将是 5 而不是 50。y 被截断。如果成员变量和虚函数相同,就会产生多态在堆栈上工作的错觉,因为使用了不同的 VTable。下面的示例说明了调用复制构造函数。变量 x 在派生类中被复制,但 y 由临时对象的初始化列表设置。
当 Base 类持有一个整数时,堆栈指针增加了 4。 y 将在作业中被截断。
在堆上使用多态时,您告诉新分配器您分配哪种类型以及您需要多少堆内存。对于堆栈,这不起作用。并且堆上的内存既不会缩小也不会增加。在初始化时,您知道要初始化的内容并准确分配了此内存量。
答案 8 :(得分:-5)
你错过了什么。您的VDerived
对象obj
的行为不符合多态,其行为与VDerived
对象完全相同。