Hacker News new | ask | show | jobs
by yati 2337 days ago
I also use C++ everyday, and find it quite powerful, but I also feel that it is ill designed. The other day I had to work with code that involved a public member function that returns a private type. You can only bind to the return value of this function with auto (because the type is private), but can otherwise do anything with the object. Why this is allowed is beyond me.

You're right that much of the software we use is written in C++, for good reasons, but asking people to "use a product of another language" just because they are critical (in a not so nice way, admitted) of it is going a bit too far. It's like asking someone to move to a different country because they think their system is "broken beyond repair".

4 comments

Your design above with the private type being returned could possibly be useful. Perhaps outsiders to the class will be prevented from instantiating the nested-type without using the class itself to generate the nested type. This could be contextually useful and certain (interface) functionality could be grouped in a manner that is more convenient to use than by calling methods on the original class.

I like that C++ allows us to express this situation, even if it's a bit of an unusual design pattern.

You can prevent instantiation of the nested type already by using private ctors and friending the wrapper class from this type, while keeping the class itself public. That is a much cleaner expression of intent. I believe that if a class is usable via its interface, it is public any way (in that you will need to go and change all uses of this object should you decide to change the nested type's API, whether it is declared private or not).

And also note that binding to the object at all has been possible only after C++11, while this "feature" works with earlier standards. That rules out the possibility of this being designed into the language with the express goal of achieving what you say (it doesn't matter, but just pointing out).

Visibiliy has always been sabout naming thing. You were always able to bind private types to template arguments (otherwise you would never been able to create an std::vector<PrivateType> or pass MyPrivateContainer to an std algorithm. What's new in C++11 is the ability to also bind locals, but that no more powerful than what you could do before (via a CPS transform).
I use this case quite commonly for implementation encapsulation. You could think of it as a capability.

The type of a lambda is inexpressible so they can only be handled with auto or decltype. I think that’s reasonable.

I am curious how you achieve encapsulation this way, please elaborate (I tried to find uses for this pattern. Even the author of the code I'm talking about, and other much better C++ programmers than myself seemed to call this an oversight rather than intent).
Here's a simple example: I have a table of stageful actions (perhaps part of a UI) that is dynamically constructed and maintained. I could register token which looks up the necessary information in an internal hash table. Of course said internal table would have to stay in sync with the UI's table. Instead I can just return an opaque object (perhaps a virtual object if life is fancy) with all the necessary state; if the UI window is deleted then all the objects will be deleted too at just the right time, with no explicit coordination with the part of code that created them (their destructors could do some bookkeeping if appropriate).

Traditional callbacks can also be used in this case, but what is a callback lambda but private state? And a stateful object rather than a lambda function is easier to manipulate, say by inspecting in the debugger or perhaps by providing a generic function for things like printing the entry.

Another case is where you have some hairball third party or legacy library that you want to be able to use in your modern code; you can often simply make it an opaque private object rather than making a complex visitor for it.

I kind of understand what you describe, but I still don't understand why the type needs to be private. You can return opaque objects of a public type just as well for this job, can't you?

    class Token {
      virtual ~Token();
      protected:
        friend class MyClass;
        ...
        /* all your state */
    };
> what is a callback lambda but private state?

You can (and do) program to the callback's interface -- say you ask for an std::function<void()>, and the user gives you that. What is behind the function (private state or not) is not your concern, you simply perform an public operation (calling it) on a public interface (i.e., via std::function).

What is the difference to you if I pass a private object or a public object all of whose instance variables and methods are private? Either way the point is to say “this is not your object to manipulate”. But making it a type you cannot even express makes that far more explicit.
I see, I guess then that's where we disagree. It is very confusing for people from my limited experience, for little gain. Plus you make it harder to do things like put these tokens in e.g., a vector without resorting to decltype.

Going back to the original point i was making with this example, I still think this was probably not a capability consciously designed in the language.

I agree this is weird, but isn't it pretty similar to the old c pattern of returning an opaque handle? I'm assuming you can't do anything with the object, since you don't know its layout and its member functions would be inaccessible. it sounds like you just get to hold onto it and pass it to any accessible functions that take it as an argument. am I misunderstanding how this works? I can't find any resources that directly explain this situation.
You can do member access into the object! That breaks encapsulation or whatever was the reason to keep the type private. Passing it around usefully isn't possible except to friend or member functions of the same class (since you can't write a function taking a type T that is private to your scope). In fact the only reason I could think of for doing something like this is to prevent people from including this type in their APIs, while still allowing to work with the object. But there are better ways to do that, including just exposing what the little object would expose via direct method calls on the enclosing class.

This isn't similar to C handles. There is usually a public API around the kind of the handle (e.g., the file oriented syscalls that works with file descriptor integers). In such cases, C handles are more like "this" pointers with an associated API.

Also, it cannot be the intent of allowing this pattern to implement opaque handles, as you can bind to them only after C++11 (auto), while such malformed code still compiles (including member access into the returned temporary) with -std=c++98.

ah okay, looks like I was mistaken. it's pretty hard to see how this could possibly be useful.
With bricks you can build a useless house with no doors, but bricks are pretty great.