Live data from Hacker News

C++ Patterns: The Badge

awesomekling.github.io

31–40 of 160 posts

Re: C++ Patterns: The Badge

#31
post #7

I like it but is access restriction really a problem? In my years of C++ programming it always seemed to be a theoretical problem more than something that leads to crashes.

This solves a chronic architectural maintenance problem in complex C++ code bases, which I've also run into countless times. Any visible interface, public or private (via friend), will eventually be used by other programmers for other than its intended use case simply because it is there and accessible. For the maintainer of an interface with a single intended purpose, what should be a clean, tidy modification within…

The best guard against unintended uses that hamstring the maintenance of the library is the good old opaque handle:

   typedef struct foo_struct *foo;

   foo foo_make();
   void foo_destroy(foo);
   void foo_frobnicate(foo, int how);
The client executables have no idea how big a foo object is, or what alignment requirements are, let alone what its members might be. The client executables don't control the allocation, construction, clean-up, freeing, or anything else.

The programmers who use this cannot just edit a header file to gain access to something; the don't have a header file which tells them anything. They could reverse engineer the data structure, but to thwart that, you could scramble the order of the structure members with each new release of the component, so their hack wouldn't be backward or forward compatible.

Re: C++ Patterns: The Badge

#32
post #7

I like it but is access restriction really a problem? In my years of C++ programming it always seemed to be a theoretical problem more than something that leads to crashes.

The C++ access restrictions exist solely to teach and enforce proper API usage by other developers. They're supposed to trigger a reconsideration of the API when a new developer runs into a restriction, but more often in my experience the new developer will just add a new public interface.

And you can add yourself a public interface even if all you have is a compiled library, and header files.

Flipping a "private:" to "public:" has no effect on the binary compatibility.

Re: C++ Patterns: The Badge

#34

Could you make the badge the last argument and give it a default value, so you don't need the initialization at the function call? It looks a bit strange and would need an explanation for why the empty initialization list is there IMO. I'm not sure if it would work, cppreference has this to say about default arguments: > The names used in the default arguments are looked up, checked for accessibility, and bound at th…

Yes, a default argument would be cleaner.

There are an astonishing number of uses for default arguments that never appear at the call site.

My fav constructs an object that is just storage for a value that needs to live a little bit longer than the call itself.

Re: C++ Patterns: The Badge

#35
C++ could solve this without badges, if it didn't have a very tiny, silly misfeature.

The misfeature is this: a class A can only declare a specific member function of class B as a friend, if that B member function is public!

Example:

The idea here is that Device has a static member function called Device::registrationHelper. That specific function (not the entire Device class) is declared a friend to VFS, and so that function can call VFS::registerDevice.

[Edit: this doesn't quite match the Badge solution, though. If this worked, it would have the problem that the registrationHelper has access to all of VFS, which is almost as bad as the entire Device class being a friend of VFS. That is one of the problems which motivate Badge. Nice try, though! The C++ restriction is probably worth fixing anyway. What the C++ friendship mechanism really needs here is to be able to say that a specific member function in B is allowed to access a specific member of A.]

  class Device;

  class VFS;

  class Device {
  public:  // we want this private!
     static void registrationHelper(VFS &v, Device &d);
  private:
     void registerWith(VFS &vfs) { registrationHelper(vfs, *this); }
  };

  class VFS {
  private:
     void registerDevice(const Device &);
     friend void Device::registrationHelper(VFS &, Device &);
  };

  void Device::registrationHelper(VFS &v, Device &d)
  {
     v.registerDevice(d);
  }
But we are forced to make Device::registrationHelper public, which defeats the purpose: anyone can call it and use it is a utility to register devices with VFSs.

If we make Device::registrationHelper private to prevent this, then the "friend void Device::registrationHelper" declaration in VFS fails.

This is an oversight; the privacy of the Device::registrationHelper identifier means that the VFS class is not even allowed to mention its name for the sake of declaring it a friend.

That should be fixed in C++. Declaring a function a friend shouldn't require it to be accessible; we are not lifting its address or calling it; we are just saying that it can call us. Allowing a function to call us is not a form of access to that function, yet is being prevented by access specifiers.

Re: C++ Patterns: The Badge

#37

Earlier quoted context omitted.

This solves a chronic architectural maintenance problem in complex C++ code bases, which I've also run into countless times. Any visible interface, public or private (via friend), will eventually be used by other programmers for other than its intended use case simply because it is there and accessible. For the maintainer of an interface with a single intended purpose, what should be a clean, tidy modification within…

> I also would guess that the compiler elides the Badge argument since it is never used. It's also zero width, so no temporaries, etc.

Problem is, nothing elides the presence of these obtrusive badge parameters in the source code.

A few bytes of stack is the least of my concerns on some file system device registration function that is called five times when the system boots.

If we imagine a software organization "going to town" with this badge approach so that there are badges all over the code base, it's not hard to imagine how it would be a nuisance.

Re: C++ Patterns: The Badge

#38
Personally I'd move the register_device() and unregister_device() functions outside of the VFS class entirely, perhaps to namespace scope.

Alternatively, there are other ways to leverage friendship and access control in C++. Here's one option:

    template 
    class DeviceManager;

    class Device {
        template friend class DeviceManager;
        int y;
    };

    class VFS {
        friend struct DeviceManager;
        int x;
    };

    template 
    struct DeviceManager {
        static void register_device (Registry& registry, Device& device) {
            registry.x = 1; // accessing privates
            device.y = 2;   // accessing privates
        }
    };

    int main() {
        VFS vfs;
        Device dev;
        DeviceManager::register_device (vfs, dev);
    }
Here all DeviceManager's can reach inside Devices, but only the VFS device manager can reach inside VFS.

This is also flexible. Remove 'static' and you get yourself a stateful mediator/manager. Add a variadic template register_devices_s_ member to Devicemanager and you've got yourself a convenience function for registering multiple devices (of heterogeneous types) without bloating the VFS API and, without runtime cost, and without losing type safety (by e.g. casting down to Device& from USBDevice&).

Re: C++ Patterns: The Badge

#39

Earlier quoted context omitted.

The C++ access restrictions exist solely to teach and enforce proper API usage by other developers. They're supposed to trigger a reconsideration of the API when a new developer runs into a restriction, but more often in my experience the new developer will just add a new public interface.

And you can add yourself a public interface even if all you have is a compiled library, and header files. Flipping a "private:" to "public:" has no effect on the binary compatibility.

> Flipping a "private:" to "public:" has no effect on the binary compatibility.

That's not the case on Windows. The access qualifier is part of the mangled name. https://en.wikiversity.org/wiki/Visual_C%2B%2B_name_mangling...

Re: C++ Patterns: The Badge

#40

Earlier quoted context omitted.

This solves a chronic architectural maintenance problem in complex C++ code bases, which I've also run into countless times. Any visible interface, public or private (via friend), will eventually be used by other programmers for other than its intended use case simply because it is there and accessible. For the maintainer of an interface with a single intended purpose, what should be a clean, tidy modification within…

The best guard against unintended uses that hamstring the maintenance of the library is the good old opaque handle: typedef struct foo_struct *foo; foo foo_make(); void foo_destroy(foo); void foo_frobnicate(foo, int how); The client executables have no idea how big a foo object is, or what alignment requirements are, let alone what its members might be. The client executables don't control the allocation, constructio…

That doesn't solve the problem the OP solves, which is to expose a method only to a specified set of other classes.
Post reply on HN