Hacker News new | ask | show | jobs
by spacechild1 279 days ago
Your socket class would have no default constructor? And you would never want to close the socket before the object's lifetime ends? Really?
2 comments

In this case, I would want the address family and protocol to be statically known, so it would have default constructor. But for example, a file might not have one, sure. As for closing before lifetime ends, why? I can just end lifetime. Wrap it in an optional if the type system can't figure it out like with a struct member.
> so it would have default constructor.

And what's the underlying value of such a default constructed socket? I assume it would be -1 resp. INVALID_SOCKET, in which case the destructor would have to deal with it.

> Wrap it in an optional if the type system can't figure it out like with a struct member.

So you essentially must wrap it in an optional if you want to use it as a member variable. I find this rather pointless as sockets already have a well-defined value for empty state (-1 resp. INVALID_SOCKET). By wrapping it in a optional you are just wasting up to 8 bytes.

Sure, you can implement a socket class like that, but it's neither necessary nor idiomatic C++.

> And what's the underlying value of such a default constructed socket? I assume it would be -1 resp. INVALID_SOCKET

No, as explained, the default value would be the result of `::socket` call, i.e. a fresh OS-level socket.

> So you essentially must wrap it in an optional if you want to use it as a member variable.

No, you only must wrap it if you really want this closed state to exist.

> Sure, you can implement a socket class like that, but it's neither necessary nor idiomatic C++.

Obviously. Because the moves are not destructive. If they were, this design would be superior. And the wasted space for optional is solvable, just like for non-nullable pointers.

> 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 case of non-nullable pointers the library author knows that they can use NULL as a sentinel value and write a corresponding specialization. But what could you possibly do with an arbitrary user-defined class?

> 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.

With destructive moves, you can end an object's lifetime whenever you want.
How would I use such a socket class as a member variable? How do I reopen the socket?
Reopen by constructing and assigning a new socket.
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.