3 ms·
The main thing that sucks about shared_ptr's is that they break C++'s limited support for covariance. For example class A { ... }; class B : public A { ..
by alexgartrell 15y ago
The main thing that sucks about shared_ptr's is that they break C++'s limited support for covariance. For example
class A { ... };
class B : public A { ... };
// Totally works
class AFactory {
public:
virtual A* makeOne();
}
class BFactory : public AFactory {
public:
virtual B* makeOne();
}
// totally doesn't
class SharedAFactory {
public:
virtual boost::shared_ptr<A> makeOne();
};
class SharedBFactory {
public:
virtual boost::shared_ptr<B> makeOne();
};
Also, from a performance perspective, locking the memory bus to do atomic operations on a shared_ptr totally blows when you're expected to turn a request around in a few microseconds.
- timr 15y agoI'll grant you the larger point that inferring relationships by template value type can be a pain. But this example is kind of bad. First off, I assume you meant to derive SharedBFactory from SharedAFactory? Otherwise, it works fine. If not, the only problem here is that C++ won't let you have a virtual function override that only differs by return type. And since it can't tell that shared_ptr<A> and shared_ptr<B> are related by value_type, it complains (this is the larger point that I granted you earlier). But the type theatrics are hiding a bigger problem.... In this example, you can either fix the type error by making makeOne() return a boost::shared_ptr<A> in all factories (in which case, you really do want a polymorphic interface), or you can make a new function that returns a different pointer type (in which case, you don't). The type system here is doing you a favor, and telling you that your design is bogus -- you shouldn't be creating a polymorphic factory interface that returns different types (because that isn't actually polymorphic, is it?) This isn't totally academic: I find that in nearly all type-error situations like this one, the real problem can be traced back to sloppy coding. "locking the memory bus to do atomic operations on a shared_ptr totally blows when you're expected to turn a request around in a few microseconds." There's always a cost to reference counting, and therefore, it's not always appropriate. That said, the only time you should be triggering atomic operations are on pointer copies and allocations. If you're doing allocations per request, kiss your microsecond response time goodbye -- the reference count overhead is the least of your worries. And you can avoid unnecessary pointer copies by passing shared_ptr<> objects by reference. With a small amount of care, it's possible to reduce shared_ptr<> overhead to essentially zero.
- MatthewCampbell 15y agoTheir polymorphic factory isn't returning "different types." They're both returning A. The derived one is just returning a specific type of A. If the shared "makeOne" returned naked pointer types A* and B* rather than shared_ptr, then this would all work correctly, thanks to C++'s covariant return type support. Covariant return types are a powerful feature in a language that doesn't support other forms of return type polymorphism. shared_ptrs function so similarly to pointers that it can be surprising when they don't support this behavior. Surprise and capability mismatch with pointers are strikes against shared_ptr. But not sufficient ones to stop me from using them.
- timr 15y ago"Their polymorphic factory isn't returning "different types." They're both returning A." No, they're not. One is returning a pointer to A, and the other is returning a pointer to B (which just happens to be a subclass of A). It matters, and just because you can do it with a naked pointer doesn't mean that you should. The problem you've described is a code smell. If I hand you a pointer to B, you (client code) can do everything that is exposed by B. If I hand you a pointer to A, you can only do the set of things exposed by A. And that's a fundamentally different interface guarantee.
- MatthewCampbell 15y agoThere are cases where covariant return types are abused, so it's fine that you treat it as a code smell. However, C++ does make them necessary for several valid patterns. This does include alexgartrell's example, but here's a popular one: I have a polymorphic type that should be copyable. Because of the limitations of C++ copy constructors and operators, my only option is to expose a virtual method (call it "copy"). The non-smelly semantics we want are such that if you call "copy" on an object, you get an identical copy of the same type. So if you call "copy" on a pointer of type A, you get an A. Call it on a B, you get a B. The contract itself is polymorphic. It doesn't commit to returning any particular flavor of A. We can implement this method if we return naked pointers. But if we want to be safe and return a smart pointer, we have to introduce dynamic casting or other worse smells.