Casting GeListNode* to BaseObject*
-
I'm rebuilding my plugins for R2024 and found an odd problem. It's more a C++ issue than the SDK, so hopefully this is the right place to post.
What happened is this. In my code there are these lines:
Bool MyPlugin::GetDDescription(const GeListNode* node, Description* description, DESCFLAGS_DESC& flags) const { BaseObject* op = static_cast<BaseObject*>(node); // fails: cannot convert from const GeListNode* to BaseObject*
This fails, the compiler can't do the conversion. But changing it to this line:
BaseObject* op = (BaseObject*)node;
Works fine! I can't see why the C-style cast works but the static_cast doesn't (and I've tried all the other casts such as dynamic_cast and they fail too). Anyone know why this happens?
Steve
-
Cause node is const, so the C-style cast is performing a const_cast.
-
That's what I thought, so I tried a const_cast like so:
BaseObject* op = const_cast<BaseObject*>(node);
And it still give the same error. Even tried:
const BaseObject* op = const_cast<BaseObject*>(node);
Same error. Only the C-style cast seems to work. It's noticeable that the C-style cast is also used in the SDK examples, and it's worth mentioning that the static_cast worked correctly (i.e. no error) in R2023.
Steve
-
You need to add the
const
to thestatic_cast
template argument, too:const BaseObject* objectPtr = static_cast<const BaseObject*>(node);
-
Thanks, that does indeed work. I thought I had tried that, but clearly not. However, although it compiles it leads to further issues. If you do this, for example:
const BaseObject* op = static_cast<const BaseObject*>(node); BaseContainer* data = op->GetDataInstance();
It won't compile, because now you need to do this:
const BaseContainer* data = op->GetDataInstance();
And if you do that, trying to change any parameter using the (now const) BaseContainer also results in errors. So to get this to work you need to do this:
const BaseObject* op = static_cast<const BaseObject*>(node); BaseContainer* data = const_cast<BaseContainer*>(op->GetDataInstance());
Which finally will compile. The annoying thing is that you might think this would work:
BaseObject* op = const_cast<BaseObject*>(node);
But it gives the same cannot convert error. However, you can do this and it will compile:
GeListNode* dnode = const_cast<GeListNode*>(node); BaseObject* op = static_cast<BaseObject*>(dnode);
And then you don't need to const_cast the BaseContainer*.
No wonder the SDK examples just stick with C-style casts!
Steve
-
Hello @spedler ,
And if you do that, trying to change any parameter using the (now const) BaseContainer also results in errors. So to get this to work you need to do this:
const BaseObject* op = static_cast<const BaseObject*>(node); BaseContainer* data = const_cast<BaseContainer*>(op->GetDataInstance());
You should never do this,
const_cast
things passed to you, as you can potentially crash Cinema 4D in doing so. Cinema 4D assumesop
to be const in this case, and you removing the constness of the data container violates this assumption. Doing this can be okay, when you defined for example op like this:const BaseObject* const op = doc->SearchObject("Foo"_s);
Here casting away the constness of
op
(or its data container) is okay, because it is only yourself who relies on that. I would avoid doing this even here though, as you might confuse yourself in the long run with doing such things. But when Cinema 4D passes you somewhere somethingconst
, e.g., aconst BaseObject* op
, then you must stick to that more than ever, because Cinema 4D is more parallelized in the backend than ever. The major point of the 2024 release is that mutability of data is a binding contract you should stick to (to allow for parallelism).When your
op
is immutable, this also means that you cannot change its data container.const BaseObject* const op; // We can get the immutable container, the version of GetDataInstance which returns // an immutable container is a const method, i.e., can be called on a const instance. // // // The const behind the parenthese marks this method (not its return value) as const. // // It is the contract that calling this method will not change the instance it is called // // upon, #this remains const. // const BaseContainer* GetDataInstance () const const BaseContainer* const bc = op->GetDataInstance();
When
op
is not const, you can also call the non-const overload ofGetDataInstance
:BaseObject* const op; // We can still call this to get the immutable data container ... const BaseContainer* const bc = op->GetDataInstance(); // ... but also this to get the mutable data container. BaseContainer* const other = op->GetDataInstance();
No wonder the SDK examples just stick with C-style casts!
Hm, we changed a crazy amount with something like 40,000 casts in our code base with 2024. This already included some parts in the SDK, but the work is far from done. Removing all the C-style casts in our code base will be quite a bit of work (it also wasn't me who had this unthankful task, so I am only speaking second hand here).
In general, I would point to our updated style guide regarding casting, I outlined there the new rules, as C-style casting is only prohibited in cases where it is dangerous. Casting atomic types in this manner is fine (but also not recomended).
Where you should be careful is casting away
const
-ness as you did above with:BaseContainer* data = const_cast<BaseContainer*>(op->GetDataInstance());
or as you can do unintentionally (which is one of the reasons why we want to avoid them) with C-style casts:
const BaseList2D* const node; // Not only up-casts #node to a BaseObject, but also implicitly removes its constness. BaseObject* const op = (BaseObject*)node;
Cheers,
Ferdinand -
Hi Ferdinand,
Thanks for this. I've read the casting guide but now I'm completely puzzled over what to do if I want to remove C-style casts from my code. In this case, GetDDescription() is passed a const GeListNode*. I want to get the BaseContainer of the BaseObject so I can change a parameter. Surely this is a common practice, but are you saying it can no longer be done?
In such a case, how do you go about it without using a C-style cast? I accept that using const_cast is dangerous...but what is the alternative?
Steve
-
Hey @spedler ,
In order to achieve the performance gains we did with 2024, we had to make some concessions between not breaking our own and third party code and actually making things faster.
As lined out in the migration guide and hinted at above, the major subject of
2024
isconst
-ness of data and methods to allow for faster scene execution by knowing when multiple things can operate on the same data at the same time (as they will all treat it as immutable) and when not.NodeData::GetDescription
is one of the cases where the method (not its return value) has becomeconst
, the signature of the method is now:2024.0 NodeData::GetDDescription
virtual Bool NodeData::GetDDescription (const GeListNode* node, Description* description, DESCFLAGS_DESC & flags ) const;
while it was this in 2023.2:
2023.2 NodeData::GetDDescription
virtual Bool NodeData::GetDDescription (GeListNode* node, Description* description, DESCFLAGS_DESC & flags );
So, when the method is called both the plugin hook instance and the node representing it to the user must be treated as immutable now. The hook must be treated as immutable because
GetDDescription
is nowconst
and therefore forces you to not changethis
. The node cannot be changed becausenode
is nowconst
. All this is done so that methods likeGetDDescription
can be called (safely) in parallel. There might be multiple entities accessingnode
orthis
when yourGetDDescription
is running. When they all had write access tothis
andnode
you might end up with access violations and crashes.Solutions
You could just cast away the const-ness of things, but you then always risk access violations and with it crashes; or even worse no crashes but corrupted data. In the migration guide I spent a whole section on different approaches of dealing with these
const
-ness changes: Constness of Overridable Methods.One of the easiest fixes is to cast away the const-ness of things but protected by a global lock. This is not a perfect solution as you either have to use the truly global lock with
GeThreadLock
andGeUnThreadLock
or use a local spin lock like I did in the example; but then be also only protected against access violations in the context of entities using this local lock. Both approaches do not truly guarantee that you will avoid access violations, just make it much more unlikely. Both approaches also come with the HUGE disadvantage that they effectively serialize the execution of these as paralell intended methods because things now must wait until the lock is accessible.Alternative approaches can be pushing things to the main thread where removing the constness will not be a problem anymore; or using more fancy things like atomic fields which can even be mutated on
const
instances.On the long run, I would recommend embracing these new design patterns rather trying to work around for them, i.e., find an architecture that does not require you to change data in
GetDDescription
. But for a quick adoption, the 2024 migration code example and guide should provide you with options.edit: So long story short, deferring things to the main thread is probably the way to go for you. But you should check first if you REALLY have to modify the data container of the node here.
Cheers,
Ferdinand -
Hi Ferdinand,
Thank you very much, that's really helpful. All I'm actually doing is implementing a dynamic description and storing some needed data in the base container. There may be another way to do that rather than using the container, if so I can avoid all this problem by changing the code. The other methods you give will all work but as you say, if I don't really need to do it, best not to in the first place.
Cheers,
Steve
-
Hey @spedler,
I would recommend having a look at the migration guide and the
example.migration_2024
in the SDK. When you are then stuck, I would invite you to either open a new thread with concrete code higghlighting your problem or reaching out to us confidentially via our contact form and share there data with us, in case you are not at liberty of sharing your code publicly.We are here to help you along some more bumpy passages of migrating your plugin.
Cheers,
Ferdinand -