为班级成员使用智能指针


159

我在理解智能指针在C ++ 11中作为类成员的用法时遇到了麻烦。我已经阅读了很多关于智能指针,我想我不知道如何unique_ptrshared_ptr/ weak_ptr做工一般。我不明白的是真正的用法。似乎每个人都建议将其unique_ptr作为几乎所有时间都使用的方式。但是我将如何实现这样的事情:

class Device {
};

class Settings {
    Device *device;
public:
    Settings(Device *device) {
        this->device = device;
    }

    Device *getDevice() {
        return device;
    }
};    

int main() {
    Device *device = new Device();
    Settings settings(device);
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

假设我想用智能指针替换指针。unique_ptr因为A 而无法使用getDevice(),对吧?那是我使用shared_ptrand的时候了weak_ptr?没有办法使用unique_ptr?在我看来,大多数情况下shared_ptr更有意义,除非我在很小的范围内使用了指针?

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> device) {
        this->device = device;
    }

    std::weak_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::weak_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

那是路要走吗?非常感谢!


4
明确了解生存期,所有权和可能为null的内容将很有帮助。例如,传递device给的构造函数后settings,您是否仍然希望能够在调用范围内或仅通过settings?如果是后者,unique_ptr则很有用。另外,您是否有返回值为的getDevice()情况null。如果没有,请返回参考。
基思

2
是的,shared_ptr在8/10的情况下a 是正确的。其他2/10在unique_ptr和之间分割weak_ptr。另外,weak_ptr通常用于中断循环引用;我不确定您的用法是否正确。
Collin Dauphinee

2
首先,您希望device数据成员拥有什么所有权?您首先必须决定。
juanchopanza

1
好的,我知道作为调用方,我可以使用a unique_ptr代替,并在调用构造函数时放弃所有权,如果我知道现在不再需要它的话。但是作为Settings该类的设计师,我不知道调用方是否也想保留引用。也许该设备将在许多地方使用。好吧,也许这正是您的意思。在那种情况下,我不会是唯一的所有者,我猜那是我将使用shared_ptr的时候。而且:如此聪明的点确实可以代替指针,而不是引用,对吗?
michaelk 2013年

this-> device =设备;也使用初始化列表。
尼尔斯

Answers:


202

unique_ptr因为A 而无法使用getDevice(),对吧?

不,不一定。这里重要的是要确定合适的所有权政策为您的Device对象,即谁将会是你的(智能)指针指向的对象的所有者。

它会成为Settings对象的单独实例吗?将Device对象必须被销毁时自动的Settings对象被销毁,还是应该活得比那个对象?

在第一种情况下,std::unique_ptr您需要的是它,因为它使Settings指向对象成为唯一(唯一)所有者,并且是导致对象破坏的唯一对象。

在这种假设下,getDevice()应该返回一个简单的观察指针(观察指针是不会使指向对象保持活动状态的指针)。最简单的观察指针是原始指针:

#include <memory>

class Device {
};

class Settings {
    std::unique_ptr<Device> device;
public:
    Settings(std::unique_ptr<Device> d) {
        device = std::move(d);
    }

    Device* getDevice() {
        return device.get();
    }
};

int main() {
    std::unique_ptr<Device> device(new Device());
    Settings settings(std::move(device));
    // ...
    Device *myDevice = settings.getDevice();
    // do something with myDevice...
}

[ 注1: 您可能想知道为什么我在这里使用原始指针,而每个人都在不断告诉原始指针不好,不安全和危险。实际上,这是一个宝贵的警告,但是将其放在正确的上下文中很重要:原始指针在用于执行手动内存管理(即,通过new和分配和释放对象)是不好的delete。当纯粹用作实现引用语义并传递非所有者的,可观察的指针的一种方法时,原始指针在本质上没有任何危险,除了可能要注意的事实是,应注意不要取消引用悬挂的指针。- 结束注释1 ]

[ 注2: 正如devicenullptrgetDevice()devicenullptr注释中所出现的那样,在这种情况下,所有权是唯一的,并且总是保证拥有对象(即内部数据成员永远不会存在),函数可以(也许应该)存在返回引用而不是指针。尽管这是真的,但我决定在此处返回一个原始指针,因为这是一个简短的答案,可以将其推广到where 可以的情况,并表明只要不使用原始指针就可以了手动内存管理。- 结束注2 ]


这种情况是完全不同的,当然,如果你的Settings目标应该具备该设备的独占所有权。例如,如果Settings对象的破坏也不应暗示指向的Device对象的破坏,则可能是这种情况。

只有作为程序设计人员的您才能知道这一点;从您提供的示例中,我很难判断是否是这种情况。

为了帮助您解决问题,您可能会问自己,是否有其他对象Settings有权保留该Device对象,只要它们持有指向该对象的指针,而不仅仅是被动观察者。如果确实如此,那么您需要一个共享所有权策略,它可以std::shared_ptr提供:

#include <memory>

class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(std::shared_ptr<Device> const& d) {
        device = d;
    }

    std::shared_ptr<Device> getDevice() {
        return device;
    }
};

int main() {
    std::shared_ptr<Device> device = std::make_shared<Device>();
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice = settings.getDevice();
    // do something with myDevice...
}

请注意,这weak_ptr是一个观察指针,而不是拥有的指针-换句话说,如果指向该对象的所有其他拥有的指针都超出范围,它不会使该指向的对象保持活动状态。

的优势weak_ptr比普通生指针,你可以放心地告诉是否weak_ptr晃来晃去与否(即它是否指向有效的对象,如果对象原来指向已被破坏)。这可以通过expired()weak_ptr对象上调用成员函数来完成。


4
@LKK:是的,正确。A weak_ptr始终是原始观察指针的替代方法。从某种意义上说,它是安全的,因为您可以在取消引用它之前检查它是否悬空,但是它也带来一些开销。如果您可以轻松地保证不会取消引用悬空指针,那么可以观察原始指针就可以了
Andy Prowl

6
在第一种情况下,最好getDevice()返回一个引用,不是吗?因此,呼叫者不必检查nullptr
vobject13年

5
@chico:不确定你的意思。auto myDevice = settings.getDevice()将创建一个Device名为的新实例,myDevice并从getDevice()返回的引用所引用的实例中复制构造该实例。如果您想myDevice成为参考,则需要做auto& myDevice = settings.getDevice()。因此,除非我缺少任何东西,否则我们将回到没有使用的情况下auto
Andy Prowl

2
@Purrformance:因为您不想放弃对象的所有权-将可修改的内容unique_ptr交给客户端会增加客户端从该对象移开的可能性,从而获得所有权并为您提供空(唯一)指针。
安迪·普罗

7
@Purrformance:虽然那样会阻止客户移动(除非客户是一个热衷于const_casts 的疯狂科学家),但我个人不会这样做。它公开了一个实现细节,即所有权是唯一的,并通过来实现unique_ptr。我的看法是这样的:如果您要/需要传递/返回所有权,请传递/返回智能指针(unique_ptrshared_ptr,取决于所有权的类型)。如果您不想/不需要通过/返回所有权,请使用(适当const限定的)指针或引用,这主要取决于参数是否可以为null。
安迪·普罗

0
class Device {
};

class Settings {
    std::shared_ptr<Device> device;
public:
    Settings(const std::shared_ptr<Device>& device) : device(device) {

    }

    const std::shared_ptr<Device>& getDevice() {
        return device;
    }
};

int main()
{
    std::shared_ptr<Device> device(new Device());
    Settings settings(device);
    // ...
    std::shared_ptr<Device> myDevice(settings.getDevice());
    // do something with myDevice...
    return 0;
}

week_ptr仅用于参考循环。依赖图必须是非循环有向图。在共享指针中,有2个引用计数:1表示shared_ptrs,1表示所有指针(shared_ptrweak_ptr)。当所有shared_ptr都被删除时,指针将被删除。如果需要来自的指针weak_ptrlock则应使用来获取指针(如果存在)。


因此,如果我正确理解了您的答案,智能指针会代替原始指针,但不一定引用吗?
michaelk 2013年

实际上,一个引用中有两个参考计数shared_ptr吗?你能解释为什么吗?据我了解,weak_ptr不必计数,因为它shared_ptr在操作对象时会创建一个新对象(如果基础对象仍然存在)。
比约恩·波莱克斯(BjörnPollex)

@BjörnPollex:我为您创建了一个简短示例:link。我还没有实现复制构造函数和的所有功能lockboost版本在引用计数方面也是线程安全的(delete仅调用一次)。
纳斯塔2013年

@Naszta:您的示例显示可以使用两个引用计数来实现此目的,但是您的答案表明这是必需的,我不相信这是必须的。您能在回答中澄清一下吗?
比约恩博动

1
@BjörnPollex,weak_ptr::lock()要判断对象是否过期,必须检查“控制块”,其中包含第一个引用计数和指向该对象的指针,因此在有任何weak_ptr对象仍在使用时,不得破坏该控制块,因此weak_ptr必须跟踪对象的数量,这是第二个引用计数的作用。当第一个引用计数降至零时,对象将被销毁,而第二个引用计数降至零时,控制块将被销毁。
乔纳森·威克利
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.