带有智能指针的返回类型协方差

问题描述 投票:0回答:5

在 C++ 中我们可以这样做:

struct Base
{
   virtual Base* Clone() const { ... }
   virtual ~Base(){}
};

struct Derived : Base
{
   virtual Derived* Clone() const {...} //overrides Base::Clone
};

但是,以下内容不会起到同样的作用:

struct Base
{
   virtual shared_ptr<Base> Clone() const { ... }
   virtual ~Base(){}
};

struct Derived : Base
{
   virtual shared_ptr<Derived> Clone() const {...} //hides Base::Clone
};

在此示例中,

Derived::Clone
隐藏
Base::Clone
而不是覆盖它,因为标准规定覆盖成员的返回类型只能从引用(或指针)更改为基类到引用(或指针) ) 来导出。有什么聪明的解决方法吗?当然,有人可能会争辩说
Clone
函数无论如何都应该返回一个普通的指针,但让我们暂时忘记它 - 这只是一个说明性示例。我正在寻找一种方法来将虚拟函数的返回类型从智能指针更改为
Base
到智能指针
Derived

提前致谢!

更新:我的第二个例子确实无法编译,感谢Iammilind

c++ smart-pointers virtual-functions return-type
5个回答
53
投票

您无法直接执行此操作,但有几种方法可以在非虚拟接口习惯用法的帮助下进行模拟。

对原始指针使用协方差,然后包装它们

struct Base
{
private:
   virtual Base* doClone() const { ... }

public:
   shared_ptr<Base> Clone() const { return shared_ptr<Base>(doClone()); }

   virtual ~Base(){}
};

struct Derived : Base
{
private:
   virtual Derived* doClone() const { ... }

public:
   shared_ptr<Derived> Clone() const { return shared_ptr<Derived>(doClone()); }
};

这仅在您实际上有一个原始指针开始时才有效。

通过强制转换来模拟协方差

struct Base
{
private:
   virtual shared_ptr<Base> doClone() const { ... }

public:
   shared_ptr<Base> Clone() const { return doClone(); }

   virtual ~Base(){}
};

struct Derived : Base
{
private:
   virtual shared_ptr<Base> doClone() const { ... }

public:
   shared_ptr<Derived> Clone() const
      { return static_pointer_cast<Derived>(doClone()); }
};

在这里,您必须确保

Derived::doClone
的所有重写实际上返回指向
Derived
或从它派生的类的指针。


3
投票

在此示例中

Derived::Clone
隐藏
Base::Clone
而不是覆盖它

,它不会隐藏它。实际上,这是一个编译错误

您不能用另一个仅在返回类型上不同的函数覆盖或隐藏虚拟函数;因此返回类型应该相同或协变,否则程序是非法的,因此会出现错误。

因此这意味着我们没有其他方法可以将

shared_ptr<D>
转换为
shared_ptr<B>
。唯一的方法是拥有
B*
D*
关系(您已经在问题中排除了这种关系)。


3
投票

@ymett 使用 CRTP 技术对出色的 answer 进行了改进。这样您就不必担心忘记在 Derived 中添加非虚函数。

struct Base
{
private:
   virtual Base* doClone() const { ... }

public:
   shared_ptr<Base> Clone() const { return shared_ptr<Base>(doClone()); }

   virtual ~Base(){}
};

template<class T>
struct CRTP_Base : Base
{
public:
   shared_ptr<T> Clone() const { return shared_ptr<T>(doClone()); }
};

struct Derived : public CRTP_Base<Derived>
{
private:
   virtual Derived* doClone() const { ... }
};

0
投票

我的脑海中浮现出一些想法。首先,如果您可以执行第一个版本,只需将

Clone
隐藏起来,and 编写另一个受保护的
_clone
实际上返回派生指针。
Clone
都可以使用它。

这就引出了一个问题:你为什么要这样做。另一种方法可能是强制(外部)函数,在该函数中,您收到一个

shared_ptr<Base>
并可以将其强制为
shared_ptr<Derived>
(如果可能)。也许是这样的:

template <typename B, typename D>
shared_ptr<D> coerce(shared_ptr<B>& sb) throw (cannot_coerce)
{
// ...
}

0
投票

通过创建自动重新路由指针的自动化实现类,您可以更接近真正的协变智能指针返回类型。这种方式也适用于自定义删除器,并且当派生类不具有它们返回的指针的唯一共享所有权时,这也适用。代码如下所示:

class Base{
    virtual void print_impl(std::shared_ptr<Base>& base_ptr) = 0;
public:
    std::shared_ptr<Base> print() {
        std::shared_ptr<Base> base;
        print_impl(base);
        return base;
    }
};

template <typename TBase>
class BaseImpl : public Base{
    void print_impl(std::shared_ptr<Base>& base_ptr) override {
        auto tbase = create();
        base_ptr = tbase;
    }
public:
    virtual std::shared_ptr<TBase> print() = 0;
 };

class Derived : public BaseImpl<Derived> {
public:
    std::shared_ptr<Derived> print();
};

在上面的例子中,所有派生类只需实现 std::shared_ptr print();并继承自BaseImpl,其中“Derived”可以是兼容类型。

对于可执行示例:

#include <iostream>
#include <memory>

class Base {
public:
    virtual ~Base() {std::cout << "Destroyed\n";}
    virtual void print() { std::cout << "Base\n"; }
};

class Derived : public Base {
public:
    void print() override { std::cout << "Derived\n"; }
};

class IFactory{
public:
    std::shared_ptr<Base> create() {
        std::shared_ptr<Base> base;
        create_impl(base);
        return base;
    }
private:
    virtual void create_impl(std::shared_ptr<Base>& base_ptr) = 0;
};

template<typename TBase>
class UFactory : public IFactory{
public:
    virtual std::shared_ptr<TBase> create() = 0;
private:
    virtual void create_impl(std::shared_ptr<Base>& base_ptr) {
        auto tbase = create();
        shared_ptr = tbase;
    }
};
class Factory : public UFactory<Base>{
public:
    std::shared_ptr<Base> create() override {
        return std::make_shared<Base>();
    }
};
class DerivedFactory : public UFactory<Derived> {
public:
    std::shared_ptr<Derived> create() override {
        return std::make_shared<Derived>();
    }
};
int main() {
    {
        std::shared_ptr<IFactory> factory = std::make_shared<DerivedFactory>();
        std::shared_ptr<Base> base = factory->create();
        std::cout << typeid(factory->create().get()).name() << std::endl; // Output: Base
        base->print(); // Output: Derived
    }
    std::cout << "-----------" << std::endl;
    {
        std::shared_ptr<DerivedFactory> factory = std::make_shared<DerivedFactory>();
        std::shared_ptr<Base> base = factory->create();
        std::cout << typeid(factory->create().get()).name() << std::endl; // Output: Derived
        base->print(); // Output: Derived
    }
    return 0;
}
© www.soinside.com 2019 - 2024. All rights reserved.