Is such a buck safe?

Suppose we have the following code:

#include <memory> #include <vector> struct BaseComponent { template <typename T> T * as() { return static_cast<T*>(this); } virtual ~BaseComponent() {} }; template <typename T> struct Component : public BaseComponent { virtual ~Component() {} }; struct PositionComponent : public Component<PositionComponent> { float x, y, z; virtual ~PositionComponent() {} }; int main() { std::vector<std::unique_ptr<BaseComponent>> mComponents; mComponents.emplace_back(new PositionComponent); auto *pos = mComponents[0]->as<PositionComponent>(); pos->x = 1337; return 0; } 

Should T * as () use static_cast or dynamic_cast? are there times when the conversion won't work? Do I need to use dynamic_cast instead?

  auto *ptr = dynamic_cast<T*>(this); if(ptr == nullptr) throw std::runtime_error("D'oh!"); return ptr; 
+4
source share
4 answers

The code you present is correct and well-formed, but casting is generally unsafe. If the actual object was not PositionComponent , then the compiler would gladly assume that it is, and you would invoke undefined behavior.

If you replace the listing with dynamic_cast , then the compiler will generate code that checks the conversion at runtime.

The real question is why do you need it. There are reasons, but more often than not, throwing is an indication of problems with your design. Review if you can do better (for example, reconfigure your code so you don't need to explicitly convert types)

+2
source

In your case, there is no way to say statically whether this correct type or not. You might want CRTP (a curiously repeating pattern template):

 template <class T> struct BaseComponent { T* as() { return static_cast<T*>(this); } virtual ~BaseComponent() {} }; template <typename T> struct Component : public BaseComponent<T> { virtual ~Component() {} }; struct PositionComponent : public Component<PositionComponent> { float x, y, z; virtual ~PositionComponent() {} }; 

That way you can:

 auto x = yourBaseComponent.as(); 

and have the type of the child type statically.

+3
source

Since you use unique_ptr<BaseComponent> , of course, there may be times when the conversion is not performed: the insertion of new data into the vector and the consumption of this data is performed in unrelated places and so that the compiler cannot enforce it.

The following is an example of an invalid click:

 struct AnotherComponent : public Component<AnotherComponent> { virtual ~AnotherComponent () {} }; std::vector<std::unique_ptr<BaseComponent>> mComponents; mComponents.emplace_back(new AnotherComponent); // !!! This code compiles, but it is fundamentally broken !!! auto *pos = mComponents[0]->as<PositionComponent>(); pos->x = 1337; 

In this regard, the use of dynamic_cast will provide better protection against misuse of the as<T> function. Note that misuse cannot be intentional: at any time when the compiler cannot check the type for you and you have a potential type mismatch, you should prefer dynamic_cast<T>

Here's a short demo to show how dynamic_cast offer you a degree of protection.

+1
source

You should always use dynamic_cast when casting polymorphic objects that are made from the base layer.

In the case when mComponents[0] not a PositionComponent (or a class derived from it), the above code will fail. Since the purpose of mComponents contain a pointer to BaseComponent is that you can put objects other than PositionComponent objects in the vector, I would say that you need to pay attention to this particular scenario.

In general, this is a "bad smell" when you use dynamic_cast (or even casting objects that are produced from a common base layer). This usually means that objects should not be stored in a common container, because they are not closely related.

+1
source

Source: https://habr.com/ru/post/1496666/


All Articles