函数应该返回指向派生类还是基类的指针?

时间:2016-02-19 22:53:29

标签: c++ pointers polymorphism

当函数需要返回一个对象时。它应该通过指向derived或base的指针返回吗?

class B{

}

class D:public B{

}

// way 1: return pointer to derived
D* createDerived(){
    D* d = new D();
    return d;
}

// way 2: return pointer to base
B* createDerived(){
    B* d = new D();
    return d;
}

我听说过“接口而不是实现的程序”,这表明我们应该返回一个指向base的指针。但是我的直觉说在这种情况下返回指向派生的指针会更好,因为如果客户端代码使用基本指针,这个函数仍然可以工作!另一方面,如果我们返回指向base的指针而客户端代码使用派生指针,则这对它们不起作用。似乎通过返回一个更“特定”的指针,我们可以为客户端代码提供更大的灵活性。

另一种看待它的方式是从“合同计划”的角度来看。其中一个建议是尽可能少地承诺。通过承诺我们将返回一个非常具体的对象,我们遵循这个规则。但是,如果我们返回一个基本指针,在我看来,我们承诺更多。

哪个更好的设计?我的推理是否正确?

我有很多关于如何制作模块化,可维护,可扩展的软件的知识,所以请原谅我的推理/结论是否正常。我对学习很感兴趣。非常感谢你的时间。

3 个答案:

答案 0 :(得分:2)

不可能以一般方式回答这个问题。特别是,返回更多派生对象会对方法的未来实现施加额外限制,而返回基类会对调用者施加更多限制。哪个最好取决于应用程序或库的设计,特别是BD提供的功能范围以及API的总体设计。

一般而言,您希望返回最大派生的,或者说松散地说,功能最强大的类,这不会限制您未来的实现选择。这允许您的客户有效地使用返回值,同时仍允许您在将来更改实现。

使用派生类D的主要缺点是您向客户端公开了更多细节,以后可能很难或无法撤消。

例如,假设您有一个方法reverse(std::ReversibleContainer &cont),它接受​​一个容器并返回它的反向快照(即,对底层容器的更改不会影响返回快照)。

在您的初始实施中,您可能决定将其实现为:

template<class BidirectionalIterator>
std::list<T> reverse(BidirectionalIterator &start, BidirectionalIterator &end) {
  std::vector output;
  std::copy(input.begin(), input.end(), back_inserter(output))
  return output;
}

稍后,您可能会意识到,对于容器(和元素)不变的某些情况,您可以避免复制基础数据,例如:

ImmutableIterator reverse(ImmutableBiderectionalIterator &input) {
  return ReversingImmutableBiderectionalIterator(input);
}

此容器可以使用输入容器为只读的知识来返回输入容器的视图,从而避免复制,它只是重新映射每个访问,从而产生与语句相同的语义。反转容器。

答案 1 :(得分:0)

我建议你将成员函数重命名为createB和createD,在第二种情况下返回指向派生类的指针,因为当反向转换可能失败时,你总是可以将派生类指针强制转换为基类。

答案 2 :(得分:0)

正确答案imo 。通常不需要从您自己的代码中的函数返回原始指针(以下例外)。

相反,只需返回创建的对象本身:

class B { virtual ~B(){} };
class D : public B {};

// this function would make real sense only if D were a class template
auto createD()
{
    return D{};
}

这在任何情况下都足够了。而且,重要的是,它表达了你得到的东西,即你可以根据需要使用D类型的对象。相反,当你得到一个原始指针时,它从第一个开始(即没有猜测函数名称的行为,阅读文档等),不清楚你允许用这个原始指针做什么。你能把它包裹在unique_ptr里面吗?你被允许删除吗?不清楚。返回一个对象(或者更好的是,精心设计的对象,这基本上意味着一致的RAII)会使您不必回答这些问题 - 只需对对象做你想做的事。

此外,如果你真的需要指针(它是对象的抽象),你仍然可以将返回包装在一个合适的智能指针中,如

auto d_uptr = std::make_unique<D>(createDerived());
auto d_sptr = std::make_shared<D>(createDerived());

或者类似地,也是智能基类指针,

std::unique_ptr<B> = std::make_unique<D>(createDerived());
std::shared_ptr<B> b_sptr = std::make_shared<D>(createDerived());

与返回D*的函数相比,它使用复制省略来构造指针并且不会产生任何开销。请注意,作为规则,指针应该是一个智能指针,因为只有这样才能直接释放出必须在代码后面的某个地方正确删除它的义务。

您需要原始指针作为函数返回类型的一个例外是克隆模式,当您想要通过基类指针复制对象时适用。这里应该使用智能指针以及用户调用的函数,但是在类中必须使用原始指针以允许协变返回类型的虚函数:

class B
{
    virtual ~B(){}
    auto clone() const
    {
         return std::unique_ptr<B>(clone_impl());
    }
protected:
    virtual B* clone_impl() const = 0;
};

class D : public B
{
protected:
    virtual D* clone_impl() const { return new D{*this}; };
};

可能还有很多其他例外情况(例如,总是在使用协方差时),目前我还没有想到这些例外情况。但那些 重要的imo。

总结:除非你有充分的理由,否则不要使用原始指针作为函数返回类型。