【问题标题】:Shared pointers do not intrinsically cast to base/derived types共享指针本质上不会转换为基/派生类型
【发布时间】:2015-11-05 19:02:33
【问题描述】:

我做了什么:我最近开始编写一个多线程的生产者-消费者式队列。最初,我使用智能指针,但最终将它们全部更改为原始指针并手动管理它们的生命周期和内存管理(如果有兴趣,请在最后编写代码)。

我在寻找什么:支持或反对这个猜想的论据:

继承不能和共享指针在同一个房间里,就像原始指针和引用对象一样。

我的推理: BaseDerived 对象是协变的。原始指针(Base*Derived*)也是如此。共享指针(shared_ptr<Base>shared_ptr<Derived>)不是。

程序员必须使用dynamic_pointer_cast 进行许多概念上不必要的向下转换,这使代码变得丑陋并且在编译时和运行时有些昂贵。

这让我想知道在面向对象的设计中是否应该避免共享指针,因为它们的好处并没有超过它们的费用和令人头疼的问题。


我修改前的代码(为了可读性,多线程省略了):

typedef shared_ptr<Animal> animal_ptr;
typedef shared_ptr<Dog> dog_ptr;

class Buffer {
private:
    mutex mu_;
    condition_variable cond_;
    deque<shared_ptr<Animal> > buffer_;
public:
    void add(shared_ptr<Animal> req) {
        std::unique_lock<std::mutex> locker(mu_);
        cond_.wait(locker, [this](){return buffer_.size() < size_;});
        buffer_.push_back(req);
        locker.unlock();
        cond_.notify_all();
    }

    shared_ptr<Animal> remove() {
        unique_lock<mutex> locker(mu_);
        cond_.wait(locker, [this](){return buffer_.size() > 0;});
        shared_ptr<Animal> back = buffer_.back();
        buffer_.pop_back();
        locker.unlock();
        cond_.notify_all();
    }
};

int main() {
    Buffer buffer;
    animal_ptr bPtr1 (new Animal()); // buffer.add() works just fine
    dog_ptr    dPtr1 (new Dog());    // EDIT: works fine too.
    animal_ptr dptr2 (new Dog());    // EDIT: it's okay
    ...
    buffer.remove();                 // returns a base class object, requires downcasting to access derived members

}

编辑以获得更多说明,以及为什么共享指针对继承的处理与原始指针不同:

void func1(shared_ptr<Animal> ptr);
void func2(Animal* ptr);
...
Dog* rawPtr = new Dog();
func1(dPtr1); // is not possible, requires upcasting
func2(rawPtr); // is ok.

【问题讨论】:

  • returns error 到底是什么意思? coliru.stacked-crooked.com/a/518ccd53897ea6b3
  • 我的第二个问题是requires upcasting before buffer.add() 是什么意思?请参阅我之前的代码示例。
  • @AndyG 您的第二条评论也是有效的。我的错误是我忘记在我的简单代码中继承 class Animal,因此所有的误解。
  • 我还是不明白有必要向上和向下...
  • @VermillionAzure 我解释了:调用 buffer.remove() 会给你一个 shared_ptr,它不会自动转换为 shahred_ptr,需要使用 dynamic_pointer_cast 进行运行时检查

标签: c++ c++11 inheritance casting shared-ptr


【解决方案1】:

不,它有效。

http://coliru.stacked-crooked.com/a/b2a83c740ed60521

#include <iostream>
#include <memory>

struct A {
  void print() { std::cout << "A" << std::endl; }
};

struct B : public A {
  void print() { std::cout << "B" << std::endl; }
};

void print(std::shared_ptr<A> a) {
    a->print();
}

int main() {
  std::shared_ptr<A> ptr_a(new A);
  std::shared_ptr<B> ptr_b(new B);
  
  ptr_a->print();
  ptr_b->print();
  
  ptr_b->A::print();
  
  //THIS WORKS!
  print(ptr_a);
  print(ptr_b);
}

最后两个“A”打印正确。你的函数不起作用的原因是因为Dog* != std::shared_ptr&lt;Dog&gt;

【讨论】:

  • 您在A::print 中缺少virtual
  • @CoffeeandCode 是的,你是对的。我想这可能是可选的,具体取决于他希望行为如何进行。
【解决方案2】:

std::shared_ptr&lt;derived&gt; 可以隐式转换为std::shared_ptr&lt;base&gt;,因此除非您使用了错误的编译器,否则在将派生指针传递给采用基指针的函数时应该没有错误。

编译良好的示例函数:

class animal{
    public:
        virtual auto print() const -> void = 0;
};

class dog: public animal{
    public:
        auto print() const -> void{ std::cout << "I'm a Dog!\n"; }
};

class cat: public animal{
    public:
        auto print() const -> void{ std::cout << "I'm not a Dog!\n"; }
};

auto func(std::shared_ptr<animal> ptr){
    ptr->print();
}

auto main() -> int{
    auto dog_ptr = std::make_shared<dog>();
    auto cat_ptr = std::make_shared<cat>();
    func(dog_ptr);
    func(cat_ptr);
}

将打印:

I'm a Dog!
I'm not a Dog!

【讨论】:

    【解决方案3】:

    现在我了解您遇到的问题。如果您始终在任何地方使用 shared_ptr,则可以像原始指针一样传递 Base 和 Derived 的 shared_ptr 对象。我充实了你给出的例子。

    当您混合 shared_ptr 和原始指针时,问题就来了。第一个问题随着重载而消失,其中 shared_ptr 函数转发给函数,该函数采用一个原始指针来完成真正的工作。当使用从共享指针获得的底层原始指针调用时,第二个问题就消失了。

    class Animal
    {
    public:
    };
    
    class Dog : public Animal
    {
    public:
    };
    
    using std::shared_ptr;
    
    typedef shared_ptr<Animal> animal_ptr;
    typedef shared_ptr<Dog> dog_ptr;
    
    class Buffer {
    private:
        std::deque<shared_ptr<Animal> > buffer_;
    public:
        void add(shared_ptr<Animal> req) {
            buffer_.push_back(req);
        }
    
        shared_ptr<Animal> remove() {
            shared_ptr<Animal> back = buffer_.back();
            buffer_.pop_back();
        }
    };
    
    void func1(Animal* ptr)
    {}
    
    void func1(shared_ptr<Animal> ptr)
    {
        func1(ptr.get());
    }
    
    void func2(Animal* ptr)
    {}
    
    
    int main() 
    {
        Buffer buffer;
        animal_ptr bPtr1(new Animal()); // buffer.add() works just fine
        dog_ptr    dPtr1(new Dog());    // requires upcasting before buffer.add()
        animal_ptr dptr2(new Dog());    // returns error, as they are covariant
    
        Dog* rawPtr = new Dog();
        func2(rawPtr); // is ok.
        func1(dPtr1); // is ok.
    
        func1(rawPtr); // requires overloading func1 with shared_ptr and raw pointer signatures
        func2(dPtr1.get()); // is okay when using underlying raw ptr
    
        return 0;
    }
    

    【讨论】:

    • 代码中没有错误。正如我所解释的,它会导致不必要的向下转换。例如,假设您有 Dog::print(),它是 Dog 特有的,而 Animal 没有它。您必须动态转换从 Buffer 获得的结果共享指针才能访问该方法。
    • @narengi 常规指针也是如此...coliru.stacked-crooked.com/a/c43cb83137794da6
    • 是的,正如 VermillionAzure 所说,常规指针也是如此。我使用 shared_ptr 实例替换原始指针没有问题。你能提供一个 shared_ptr 行为不同的例子吗?
    • @VermillionAzure 如果有意义,请查看编辑。
    • @narengi 好。您应该首先使用该示例打开。
    猜你喜欢
    • 1970-01-01
    • 2020-03-16
    • 1970-01-01
    • 2013-09-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-22
    相关资源
    最近更新 更多