共享指针本质上不强制转换为基/派生类型

Shared pointers do not intrinsically cast to base/derived types

本文关键字:派生 类型 转换 指针 本质上 共享      更新时间:2023-10-16

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

我要找的是:支持或反对这个猜想的论据:

继承不能和共享指针共处一室,就像原始指针和引用对象一样。

我的推理: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

}

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

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.

不,它工作。

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<Dog> .

std::shared_ptr<derived>隐式地转换为std::shared_ptr<base>,因此将派生指针传递给接受基指针的函数应该没有错误,除非您使用的编译器不好。

一个编译良好的函数示例:

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!

现在我明白你遇到的问题了。如果始终在任何地方使用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;
}