代码改变世界

[翻译]将智能指针用于C++的类成员

2014-02-14 19:29  付哲  阅读(1988)  评论(0编辑  收藏  举报

http://stackoverflow.com/questions/15648844/using-smart-pointers-for-class-members

Question:

I'm havingtrouble understanding the usage of smart pointers as class members in C++11. Ihave read a lot about smart pointers and I think I do understand howunique_ptrandshared_ptr/weak_ptrwork in general. What I don't understand is the real usage. It seems likeeverybody recommends usingunique_ptr as the way to go almostall the time. But how would I implement something like this:


我无法理解该如何将C++11的智能指针用作类的成员。我看过很多有关智能指针的东西,我确信我知道unique_ptr、shared_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...
}

 

Let's say Iwould like to replace the pointers with smart pointers. A unique_ptr would not workbecause of getDevice(), right? So that's the time when I useshared_ptrandweak_ptr?No way of usingunique_ptr? Seems to me like for most casesshared_ptrmakes more sense unless I'm using a pointer in a really small scope?

我想将原始指针换成智能指针。因为有getDevice(),所以不能用unique_ptr(因为所有权不再唯一了),对吗?所以这时候我该用shared_ptr和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...
}

 

Is that the wayto go? Thanks very much!

Answer:

A unique_ptr would not workbecause of getDevice(), right?

No, notnecessarily. What is important here is to determine the appropriate ownershippolicy for your Device object, i.e. who is going to be the owner of the objectpointed to by your (smart) pointer.

因为有getDevice(),所以不能用unique_ptr,对吗?

不,没那么绝对。现在重要的是针对你的Device对象确定一个适合的所有权策略,即,在指向这个对象的指针中,哪个拥有它的所有权(有责任决定对象的生命期)?

Is it going tobe the instance of the Settings object alone? Will the Device object have to bedestroyed automatically when the Settings object gets destroyed, or should itoutlive that object?

只由Settings对象拥有它吗?当Settings对象析构时Device对象也要自动被析构吗?还是Device对象的生命期可以比Settings对象更长?

In the firstcase, std::unique_ptr is what you need, since it makes Settings the only(unique) owner of the pointed object, and the only object which is responsiblefor its destruction.

第一种情况下,std::unique_ptr就是你需要的,Settings对象独自拥有被指向的Device对象的所有权,因此析构Device对象就是它的责任。

Under thisassumption, getDevice() should return a simple observing pointer (observingpointers are pointers which do not keep the pointed object alive). The simplestkind of observing pointer is a raw pointer:

这种假设下,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...
}

 

[NOTE 1: You may be wondering why I amusing raw pointers here, when everybody keeps telling that raw pointers arebad, unsafe, and dangerous. Actually, that is a precious warning, but it isimportant to put it in the correct context: raw pointers are bad when used for performing manual memorymanagement, i.e. allocating and deallocating objects through new anddelete. When used purely as a means to achieve reference semantics and passaround non-owning, observing pointers, there is nothing intrinsically dangerousin raw pointers, except maybe for the fact that one should take care not todereference a dangling pointer. -ENDNOTE 1]

[注意1:你可能纳闷为什么我在这用了原始指针,因为所有人都在告诉你原始指针不好、不安全、很危险。事实上这个警告很宝贵,但重要的是要将它放在正确的上下文中:当用于执行手动的内存管理时用原始指针并不合适,即通过new和delete分配和释放对象时。但如果只是用于实现引用语义,不涉及所有权,作为观察指针时,原始指针本质上并不危险,除了可能需要牢记不要解引用一个悬挂指针。——注意1结束]

[NOTE 2: As it emerged in the comments,in this particular case where the ownership is uniqueand the owned object is always guaranteed to be present (i.e. theinternal data member device is never going to be nullptr), function getDevice()could (and maybe should) return a reference rather than a pointer. While thisis true, I decided to return a raw pointer here because I meant this to be ashort answer that one could generalize to the case where device could benullptr, and to show that raw pointers are OK as long as one does not use themfor manual memory management. -END NOTE2]

[注意2:正如评论中所说,在这种特殊情况下——所有权唯一,而且,拥有的对象保证一直存在(内部数据成员device永远不会变为nullptr),getDevice()可以(或者是应该)返回一个引用而不是指针。虽然这是事实,我决定返回一个原始指针是因为这个答案很短,还可以推广到device可以为nullptr的情况,此时用原始指针也没有问题,只要没有人将它们用于手动内存管理就可以。——注意2结束]

The situation isradically different, of course, if your Settings object shouldnot have the exclusive ownership of thedevice. This could be the case, for instance, if the destruction of theSettings object should not imply the destruction of the pointed Device objectas well.

如果你的Settings对象不应该拥有device的唯一所有权,情况当然就很不一样了。这就意味着,Settings对象的析构不应该隐含着device指向的Device对象也要析构。

This issomething that only you as a designer of your program can tell; from theexample you provide, it is hard for me to tell whether this is the case or not.

作为你的程序的设计者,只有你能告诉我们到底是什么情况;从你提供的例子中,我很难看出是哪种。

To help youfigure it out, you may ask yourself whether there are any other objects apartfrom Settings that are entitled to keep the Device object alive as long as theyhold a pointer to it, instead of being just passive observers. If that isindeed the case, then you need a sharedownership policy, which is what std::shared_ptr offers:

为了帮助你解决,你应该问自己,有没有Settings以外的对象有资格在持有Device指针期间保持Device对象存在,而不仅仅是被动的观察者。如果有这样的对象,你需要的是分享所有权策略,这就是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...
}

 

Notice, that weak_ptris an observing pointer, not an owning pointer - in other words, it does notkeep the pointed object alive if all other owning pointers to the pointedobject go out of scope.

注意,weak_ptr是观察指针,而不是所有权指针——也就是说,如果离开了所有其它拥有这个对象的指针所在的作用域,它没办法保持对象存在。

The advantage ofweak_ptrover a regular raw pointer is that you can safely tell whetherweak_ptris dangling or not (i.e. whether it is pointing to a valid object, or if theobject originally pointed to has been destroyed). This can be done by callingthe expired() member function on theweak_ptr object.

weak_ptr相比于普通的原始指针的优势在于你可以很安全的分辨出weak_ptr是不是悬挂指针(它是指向有效的对象,还是它本来指向的对象已经被摧毁了)。这可以通过对weak_ptr对象调用expired()成员函数来实现。

@LKK: Yes,correct. A weak_ptr is always an alternative to raw observing pointers. Itis safer in a sense, because you could check if it is dangling beforedereferencing it, but it also comes with some overhead. If you can easilyguarantee that you are not going to dereference a dangling pointer, then youshould be fine with observing raw pointers

@LKK:对,weak_ptr总是原始指针的另一种选择。某种意义上它更安全,因为你能在解引用前检查它是不是悬挂状态,但这也带来了一些开销。如果能轻松保证不会解引用一个悬挂指针,你可以放心将原始指针当作观察用。

@vobject: Also,returning a reference would make it possible for the user to erroneously writesomething like: Device myDevice = settings.getDevice(); when they actuallymeant Device& myDevice = settings.getDevice(); (forgetting the &),which won't happen when returning a pointer. I also feel like checking againstnullptr is not needed here, because the function is always guaranteed to returna valid pointer.

@vobject:同样的,返回一个引用也可能会导致使用者写下这样的错误代码:

Device myDevice =settings.getDevice();

实际上他们想写的是:

Device&myDevice = settings.getDevice(); //忘了写&

如果返回指针的话就不会发生这种事了。我还感觉不需要检查nullptr,因为这个函数保证了始终会返回一个有效指针。

@vobject: Inthis case it could return a reference, yes, or a reference wrapper. However,this can't be generalized to the case where a returned pointer could be null,so I thought I'd just put a pointer in there (my main goal in this answer wasto explain the meaning of observing pointers vs owning pointers in ageneralized context, and to point out that raw pointers are not that bad whenobserving pointers are needed).

@vobject:这种情况下可以返回一个指针,或是一个包装器引用。但不能将它推广到可能返回null指针的情况,所以我想我只需要在那放个指针(答案里我的主要目的是解释观察指针相比于所有权指针在一个一般化的上下文中的含义,并指出在需要观察指针时,原始指针并没有那么糟糕)。

@chico: Not surewhat you mean. auto myDevice = settings.getDevice() will create a new instanceof type Device called myDevice and copy-construct it from the one referenced bythe reference that getDevice() returns. If you want myDevice to be a reference,you need to do auto& myDevice = settings.getDevice(). So unless I ammissing something, we're back in the same situation we had without using auto.

@chico:不清楚你想说什么。auto myDevice =settings.getDevice()会创建一个名叫myDevice的Device类型新对象,并且会通过getDevice()返回的引用来进行复制构造。如果你想要让myDevice是一个引用,你应该写auto& myDevice =settings.getDevice()。所以如果我没有错过什么的话,我们还是回到没有用auto的情况吧。

@BretKuhns: As Imentioned in a previous comment, it is true that in this case we could return areference, but my goal in the answer was just to provide a simple solution that1) could be generalized (applies also when the allowed pointer could be null),and 2) showed that raw pointers are bad only when used for manual memorymanagement. But I agree that in this particular case we could/should return areference.

@BretKuhns:正如我在前面的评论中提到的,这种情况下返回一个引用很正确,但我答案的目的只是提供一个简单的解决方案1)能被一般化(也能应用于允许返回null指针的情况),2)表明原始指针只有在用于手动内存管理时才很糟糕。但我同意在这种特殊情况下我们可以/应该返回一个引用。