为什么我要在这种情况下使用智能指针?
Why would I want to use a smart pointer in this situation?
我从未使用过任何类型的智能指针,但当主题是指针时,我几乎到处都在阅读它们。我确实理解在某些情况下智能指针比原始指针更好用,因为在某种程度上它们管理指针的所有权。但是,我仍然不知道,"I do not needing smart pointers for that"和"this is a case for smart pointers"之间的界线在哪里。
比方说,我有以下情况:
class A {
public:
double get1(){return 1;}
double get2(){return 2;}
};
class SomeUtilityClass {
public:
SomeUtilityClass(A* a) : a(a) {}
double getResult(){return a->get1() + a->get2();}
void setA(A* a){a = a;}
private:
A* a;
};
int main(int argc, char** argv) {
A a;
SomeUtilityClass u(&a);
std::cout << u.getResult() << std::endl;
A a2;
u.setA(&a2);
std::cout << u.getResult() << std::endl;
return 0;
}
这当然是一个过于简单化的例子。我的意思是 SomeUtilityClass
不应该 "own" A
的一个实例(因为它只是一个实用程序 class),因此它只包含一个指针。
关于指针,我知道唯一可能出错的是:
SomeUtilityClass
可以用空指针实例化
- 指向的对象可能 deleted/go 超出范围,
SomeUtilityClass
没有注意到它
智能指针如何帮助避免这个问题?在这种情况下使用智能指针还有什么其他好处?
PS:我知道有几个关于智能指针的问题(例如this one)。但是,如果您能告诉我对这个特定示例的影响,我将不胜感激。
如果SomeUtilityClass
不拥有成员变量a
,那么智能指针就没有意义。
您可以考虑引用成员,这将消除空指针的问题。
这取决于参数的创建和存储方式。如果您不拥有内存并且它可以静态或动态分配,那么原始指针是一个非常合理的解决方案 - 特别是如果您需要像示例中那样支持数据交换。另一种选择是使用 std::reference_wrapper
,这将消除您的 nullptr
问题,同时保持相同的语义。
如果您持有指向某个共享资源的指针(即存储在 std::shared_ptr
某处)并且希望能够检查它是否已被删除,您可以持有 std::weak_ptr
.
C++中默认的非拥有指针的表达方式是weak_ptr
。要使用 weak_ptr
您需要使用 shared_ptr
作为所有权,因此在您的示例中您将使用
shared_ptr<A> owner(...)
而不是
A a
然后作为你 SomeUtilityClass
的私有指针成员,你使用弱指针:
weak_ptr<A> w;
并用shared_ptr
初始化它:
SomeUtilityClass(shared_ptr<A> o) : w(o) {}
但是,您不能直接使用 weak_ptr
,因为 shared_ptr
可能超出范围并且您的弱指针无法再指向任何东西。使用前需要锁定:
shared_ptr<A> locked = w.lock();
如果拥有指针不再管理对象,locked
指针将为空,因为例如它超出了范围。如果它不为空,你可以使用它,然后它会超出范围自动释放对象的锁。
shared_ptr
和 weak_ptr
在 C++11 的标准库中可用,在旧编译器的 Boost 中可用。
有不同类型的智能指针。在你的情况下,很明显智能指针并不是真正需要的,但它仍然可以提供一些好处。
SomeUtilityClass can be instantiated with a null pointer
这个问题可能最好通过检查构造函数来解决,在您获得 NULL 指针作为参数的情况下抛出异常或以其他方式指示错误。我很难想象智能指针会有什么帮助,除非您使用不接受 NULL 的特定智能指针 class,所以它已经为您进行了检查。
The object pointed to may be deleted/go out of scope, without the
SomeUtilityClass noticing it
这个问题实际上可以用一种特殊类型的智能指针来解决,但是需要被指向的对象以某种方式支持销毁通知。一个这样的例子是Qt库中的QPointer
class,它只能指向QObject
个实例,删除时会通知它,所以当对象被删除时智能指针自动变为NULL .不过,这种方法存在一些问题:
- 每次通过智能指针访问对象时都需要检查是否为NULL。
- 如果智能指针指向class的一个实例,比如说
MyClass
,扩展class执行删除通知( QObject
在 Qt 的情况下),你会得到奇怪的结果:通知智能指针的是 QObject 的析构函数,所以当 MyClass
析构函数已经开始它的肮脏工作时你可能会访问它,所以对象已部分销毁,但指针尚未为 NULL,因为销毁仍在进行中。
为了这个答案的目的,我将 setA 重新定义为:
void setA(A* new_a){a = new_a;}
考虑:
// Using your SomeUtilityClass
int main() {
A a;
SomeUtilityClass u(&a);
// We define a new scope, just because:
{
A b;
u.setA(&b);
}
std::cout << u.getResult() << '\n';
return 0;
}
范围完成后,SomeUtilityClass
有一个悬空指针,getResult()
调用未定义的行为。请注意,这不能通过参考来解决:您仍然会得到一个悬空的。
现在考虑使用智能指针的版本:
class SomeUtilityClass {
public:
SomeUtilityClass(std::shared_ptr<A>& a) : a{a} {}
double getResult(){return a->get1() + a->get2();}
void setA(std::shared_ptr<A>& new_a){a = new_a;}
private:
std::shared_ptr<A> a;
};
int main() {
std::shared_ptr<A> a{new A};
SomeUtilityClass u{a};
// We define a new scope, just because:
{
std::shared_ptr<A> b{new A};
u.setA(b);
}
std::cout << u.getResult() << '\n';
return 0;
}
因为您拥有共享所有权,所以无法获得悬挂指针。 b
指向的内存会像往常一样被删除,但只有在u
被销毁(或者它的指针被改变)之后才会被删除。
恕我直言,在大多数情况下你应该使用智能指针(即使一开始它似乎没有多大意义)。它使维护更容易。仅在实际需要它们的特定代码中使用原始指针,并且 encapsulate/isolate 尽可能多地使用此代码。
我从未使用过任何类型的智能指针,但当主题是指针时,我几乎到处都在阅读它们。我确实理解在某些情况下智能指针比原始指针更好用,因为在某种程度上它们管理指针的所有权。但是,我仍然不知道,"I do not needing smart pointers for that"和"this is a case for smart pointers"之间的界线在哪里。
比方说,我有以下情况:
class A {
public:
double get1(){return 1;}
double get2(){return 2;}
};
class SomeUtilityClass {
public:
SomeUtilityClass(A* a) : a(a) {}
double getResult(){return a->get1() + a->get2();}
void setA(A* a){a = a;}
private:
A* a;
};
int main(int argc, char** argv) {
A a;
SomeUtilityClass u(&a);
std::cout << u.getResult() << std::endl;
A a2;
u.setA(&a2);
std::cout << u.getResult() << std::endl;
return 0;
}
这当然是一个过于简单化的例子。我的意思是 SomeUtilityClass
不应该 "own" A
的一个实例(因为它只是一个实用程序 class),因此它只包含一个指针。
关于指针,我知道唯一可能出错的是:
SomeUtilityClass
可以用空指针实例化- 指向的对象可能 deleted/go 超出范围,
SomeUtilityClass
没有注意到它
智能指针如何帮助避免这个问题?在这种情况下使用智能指针还有什么其他好处?
PS:我知道有几个关于智能指针的问题(例如this one)。但是,如果您能告诉我对这个特定示例的影响,我将不胜感激。
如果SomeUtilityClass
不拥有成员变量a
,那么智能指针就没有意义。
您可以考虑引用成员,这将消除空指针的问题。
这取决于参数的创建和存储方式。如果您不拥有内存并且它可以静态或动态分配,那么原始指针是一个非常合理的解决方案 - 特别是如果您需要像示例中那样支持数据交换。另一种选择是使用 std::reference_wrapper
,这将消除您的 nullptr
问题,同时保持相同的语义。
如果您持有指向某个共享资源的指针(即存储在 std::shared_ptr
某处)并且希望能够检查它是否已被删除,您可以持有 std::weak_ptr
.
C++中默认的非拥有指针的表达方式是weak_ptr
。要使用 weak_ptr
您需要使用 shared_ptr
作为所有权,因此在您的示例中您将使用
shared_ptr<A> owner(...)
而不是
A a
然后作为你 SomeUtilityClass
的私有指针成员,你使用弱指针:
weak_ptr<A> w;
并用shared_ptr
初始化它:
SomeUtilityClass(shared_ptr<A> o) : w(o) {}
但是,您不能直接使用 weak_ptr
,因为 shared_ptr
可能超出范围并且您的弱指针无法再指向任何东西。使用前需要锁定:
shared_ptr<A> locked = w.lock();
如果拥有指针不再管理对象,locked
指针将为空,因为例如它超出了范围。如果它不为空,你可以使用它,然后它会超出范围自动释放对象的锁。
shared_ptr
和 weak_ptr
在 C++11 的标准库中可用,在旧编译器的 Boost 中可用。
有不同类型的智能指针。在你的情况下,很明显智能指针并不是真正需要的,但它仍然可以提供一些好处。
SomeUtilityClass can be instantiated with a null pointer
这个问题可能最好通过检查构造函数来解决,在您获得 NULL 指针作为参数的情况下抛出异常或以其他方式指示错误。我很难想象智能指针会有什么帮助,除非您使用不接受 NULL 的特定智能指针 class,所以它已经为您进行了检查。
The object pointed to may be deleted/go out of scope, without the SomeUtilityClass noticing it
这个问题实际上可以用一种特殊类型的智能指针来解决,但是需要被指向的对象以某种方式支持销毁通知。一个这样的例子是Qt库中的QPointer
class,它只能指向QObject
个实例,删除时会通知它,所以当对象被删除时智能指针自动变为NULL .不过,这种方法存在一些问题:
- 每次通过智能指针访问对象时都需要检查是否为NULL。
- 如果智能指针指向class的一个实例,比如说
MyClass
,扩展class执行删除通知(QObject
在 Qt 的情况下),你会得到奇怪的结果:通知智能指针的是 QObject 的析构函数,所以当MyClass
析构函数已经开始它的肮脏工作时你可能会访问它,所以对象已部分销毁,但指针尚未为 NULL,因为销毁仍在进行中。
为了这个答案的目的,我将 setA 重新定义为:
void setA(A* new_a){a = new_a;}
考虑:
// Using your SomeUtilityClass
int main() {
A a;
SomeUtilityClass u(&a);
// We define a new scope, just because:
{
A b;
u.setA(&b);
}
std::cout << u.getResult() << '\n';
return 0;
}
范围完成后,SomeUtilityClass
有一个悬空指针,getResult()
调用未定义的行为。请注意,这不能通过参考来解决:您仍然会得到一个悬空的。
现在考虑使用智能指针的版本:
class SomeUtilityClass {
public:
SomeUtilityClass(std::shared_ptr<A>& a) : a{a} {}
double getResult(){return a->get1() + a->get2();}
void setA(std::shared_ptr<A>& new_a){a = new_a;}
private:
std::shared_ptr<A> a;
};
int main() {
std::shared_ptr<A> a{new A};
SomeUtilityClass u{a};
// We define a new scope, just because:
{
std::shared_ptr<A> b{new A};
u.setA(b);
}
std::cout << u.getResult() << '\n';
return 0;
}
因为您拥有共享所有权,所以无法获得悬挂指针。 b
指向的内存会像往常一样被删除,但只有在u
被销毁(或者它的指针被改变)之后才会被删除。
恕我直言,在大多数情况下你应该使用智能指针(即使一开始它似乎没有多大意义)。它使维护更容易。仅在实际需要它们的特定代码中使用原始指针,并且 encapsulate/isolate 尽可能多地使用此代码。