Live data from Hacker News

The repercussions of missing an Ampersand in C++ and Rust

nablag.com

111–120 of 128 posts

Re: The repercussions of missing an Ampersand in C++ and Rust

#111

Earlier quoted context omitted.

In practice, move operations typically just leave an empty object behind. The destructor already has to deal with that. And of course you can't call certain methods on an empty object. So in practice you don't need special logic except for the move operations themselves.

> The destructor already has to deal with that. That's partly true, partly circular. Because moves work this way, it's harder to make a class that doesn't have empty states, so I don't design my class to avoid empty states, so the destructor has to handle them.

> That's partly true, partly circular.

I don't think there is anything "partly" about it being true. A moved-from object is expected to remain valid and preserve class invariants. If you wrote a class whose objects fails to remain valid after being moved,you wrote bugs into your code.

> Because moves work this way, it's harder to make a class that doesn't have empty states, so I don't design my class to avoid empty states, so the destructor has to handle them.

You are not required to implement an empty state. You are only required to write your classes so that after moving an object it remains valid. You are free to specify what this means to your classes, and can be anything from leaving the object as if it was default initialized or have literally a member variable such as bool moved. It's up to you. In C++'s perspective as long as your moved-from object can be safely destroyed them it's all good. Anything else is the behavior you chose to have, and bugs you introduced.

Re: The repercussions of missing an Ampersand in C++ and Rust

#112
post #87

Earlier quoted context omitted.

It's not like it's the only part of the language that mandates a default constructor though. There are plenty of situations where default-constructible types are desirable. Even simple things like having a non-default-constructible type in a map is awkward.

> It's not like it's the only part of the language that mandates a default constructor though It’s… not a part of the langage which mandates a default ctor in the first place.

> It’s… not a part of the langage which mandates a default ctor in the first place.

Why should it, tough? Think about it. The goal of move semantics is performance, mainly avoiding to copy/initialize expensive objects using a standard syntax. Why do you believe it would be a good idea to force constructors when they can very well be the reason why move should be used?

Re: The repercussions of missing an Ampersand in C++ and Rust

#113

Earlier quoted context omitted.

First, one shouldn't use a moved-from object in the first place (except for, maybe, reassigning it). Second, why can't the .street() method simply return an empty string in this case? > The moved-from value has to be valid after move (all of its invariants need to hold) The full quote from the C++ standard is: "Unless otherwise specified, such moved-from objects shall be placed in a valid but unspecified state" AFAIK…

> First, one shouldn't use a moved-from object in the first place (except for, maybe, reassigning it). It still requires you to come up with somethkng to do to the old value in the move constructor. What would you do in the ValidatedAddress case? Set a flag in the struct called “moved_from” and use that to throw an exception if it’s ever used? Wouldn’t it be nice if you just didn’t need to worry about it? > Second, w…

> Wouldn’t it be nice if you just didn’t need to worry about it?

Do you worry about it? I mean, to begin with, do you purposely try to reuse objects that you explicitly moved? If you do, in the very least you can be lazy and reassign a newly constructed object right after you explicitly move its contents, but I don't see any reason that would justify such a thing.

Can you point out what you feel is the scenario that worries you the most?

Re: The repercussions of missing an Ampersand in C++ and Rust

#115
post #99

Earlier quoted context omitted.

> It's not like it's the only part of the language that mandates a default constructor though It’s… not a part of the langage which mandates a default ctor in the first place.

It doesn't, but it does mandate that the object has some "empty state". If you have an empty state you might as well have a default constructor which initializes the object to that empty state.

moved-from objects are not in an empty state but in an unspecified state, they are only required to be destructible, every other operation can be disallowed. That is not a useful state for default construction. Thus being movable does not imply defaulting is any sort of good idea.

The other way around makes more sense, but even then it is not systematic, if default construction is costly (allocation, syscall, …) then you don’t want to do that for a moved-from object which will just be destroyed, which is the fate of most.

Re: The repercussions of missing an Ampersand in C++ and Rust

#116
post #101

Earlier quoted context omitted.

> I wonder what you are doing to get yourself in that situation. The problem with the current move semantics is that, compared to e.g. Rust: 1) the compiler generates unnecessary code and 2) instead of just implementing class T you must implement a kind of optional . Which means, that after all those years of using smart pointers I find myself ditching them in favor of plain pointers like we did in the 90's.

> The problem with the current move semantics is that, compared to e.g. Rust: 1) the compiler generates unnecessary code and 2) instead of just implementing class T you must implement a kind of optional . I don't know what you mean by "compiler generates unnecessary code" or why you see that as a problem. I also have no idea what you mean by "a kind of optional". The only requirement on moved-from objects is that the…

The compiler generates code for calling the destructor after the object was moved. This was problem #1.

Regarding #2, take Resource Acquisition Is Initialization (RAII) as an example - in RAII, the existence of an object implies the existence of a resource. Now, if you want to be able to move, the object becomes "either the resource exists or it was moved out". As someone else noted in the comments, this affects not only the destructor. Methods cannot assume the existence of the resource, they have to check it first. Kind of like optional.

Re: The repercussions of missing an Ampersand in C++ and Rust

#117

Earlier quoted context omitted.

So I essentially have to wrap it in something like std::optional. Well, that's certainly one way to write a socket class, but I'd say it's not idiomatic C++. (I have never seen a socket class being implemented like that.)

You don't need optional in this case, the assignment would just destroy the old socket and immediately move the new one in its place.

Well, reopening a socket implies that I have manually closed the socket, which does require an optional with your implementation.

Re: The repercussions of missing an Ampersand in C++ and Rust

#118

Earlier quoted context omitted.

> It's not like it's the only part of the language that mandates a default constructor though It’s… not a part of the langage which mandates a default ctor in the first place.

> It’s… not a part of the langage which mandates a default ctor in the first place. Why should it, tough? Think about it. The goal of move semantics is performance, mainly avoiding to copy/initialize expensive objects using a standard syntax. Why do you believe it would be a good idea to force constructors when they can very well be the reason why move should be used?

Did you reply to the wrong comment?

Re: The repercussions of missing an Ampersand in C++ and Rust

#119

Earlier quoted context omitted.

> If they were, this design would be superior. I see how destructive moves would slightly simplify the implementation, but what difference would it make apart from that? (Don't get me wrong, I totally think that destructive moves are a good idea in general, I just don't see the qualitative difference in this particular case.) > And the wasted space for optional is solvable, just like for non-nullable pointers. In the…

> what difference would it make The same difference as making pointers always non-nullable and reintroducing nullability via an optional wrapper only when semantically appropriate. > what could you possibly do with an arbitrary user-defined class Just add some customization points to std::optional so that users can define which value of the class to treat as noneopt internally.

> The same difference as making pointers always non-nullable and reintroducing nullability via an optional wrapper only when semantically appropriate.

Again, I don't see what this has to do with destructive moves. If you want a socket class that always refer to an open socket, you can already do that. Same for non-nullable pointer wrappers. Conversely, destructive moves don't prevent you from implementing a socket class with a close() method. These concepts are really orthogonal.

> Just add some customization points to std::optional so that users can define which value of the class to treat as noneopt internally.

How is this supposed to work? The very point of your socket class is that it always contains a valid socket handle. Once you introduce a sentinel value, you are back to square one. If the optional class is able to construct a socket with the sentinel value, so is the user.

Re: The repercussions of missing an Ampersand in C++ and Rust

#120

Earlier quoted context omitted.

> what difference would it make The same difference as making pointers always non-nullable and reintroducing nullability via an optional wrapper only when semantically appropriate. > what could you possibly do with an arbitrary user-defined class Just add some customization points to std::optional so that users can define which value of the class to treat as noneopt internally.

> The same difference as making pointers always non-nullable and reintroducing nullability via an optional wrapper only when semantically appropriate. Again, I don't see what this has to do with destructive moves. If you want a socket class that always refer to an open socket, you can already do that. Same for non-nullable pointer wrappers. Conversely, destructive moves don't prevent you from implementing a socket cl…

> Again, I don't see what this has to do with destructive moves. If you want a socket class that always refer to an open socket, you can already do that.

Technically you can, but it's unreasonable to create an os-level socket just to put into the moved-out object where it will be immediately destroyed again. This is not an issue when the moves are destructive.

> How is this supposed to work? The very point of your socket class is that it always contains a valid socket handle. Once you introduce a sentinel value, you are back to square one. If the optional class is able to construct a socket with the sentinel value, so is the user.

That's not true. The sentinel value need not be exposed in the public interface of the class, it can only be accessible via the customization point of the optional.

Post reply on HN