1

I have some (in my opinion) fairly specific ownership requirements: I have a class that basically interprets an array of doubles is a specific way (a concatenation of some fairly large matrices), and want to communicate with a C library that interprets then in another way (a very long mathematical vector). In some cases, I want to interpret a pointer that is passed to a callback by the C library, that is, without taking ownership. In that case, copying would be very impractical. In other cases, I want to allocate the buffer myself, and pass it to the C library myself. In that case, my code owns buffer.

I created a "building block" that interprets the double array as the matrices (with boost::numeric::ublas::shallow_array_adaptor, but that is mostly irrelevant), like this:

class Foo {
 public:
  explicit Foo(double *buffer);
  Foo(const Foo &) = delete;
  Foo(Foo &&) = delete;
  Foo &operator=(const Foo &) = delete;
  /* Some accessors. */
 protected:
  Foo &operator=(Foo &&) = default;
 private:
  /* Some things that store pointers into the buffer. */
};

Copying and moving is forbidden so that and instance cannot accidentally be created or moved somewhere which outlives the buffer itself. Of course, deliberate creation of such instances is possible by directly passing the pointer somewhere but can be spotted more easily in the source.

The first part of my question: Does it make sense to let "Foo enhanced with ownership of the buffer" be a subclass of Foo?

Every operation of Foo is possible with the owning-Foo, additionally, owning-Foo can be freely copied and moved. It smells like the Liskov substitution principle is satisfied. Being able to handle owning-Foo and Foo the same way syntactically without writing a bunch of methods in owning-Foo that delegate to a member variable is very comfortable.

On the other hand, there could be owner-of-Foo instead, that deals with the ownership and nothing else, and contains an instance of Foo that may be accessed from outside, providing better separation of concerns.

I implemented owning-Foo like this:

class OwningFoo : private std::unique_ptr<double[]>, public Foo {
 public:
  explicit OwningFoo(std::size_t size)
      : std::unique_ptr<double[]>(new double[size]),
        Foo(std::unique_ptr<double[]>::get()), size_(size) {
  }
  /* Implementation of copy and move constructors and
   * assignment operators redacted. */
  OwningFoo(const OwningFoo &);
  OwningFoo(OwningFoo &&);
  OwningFoo &operator=(const OwningFoo &);
  OwningFoo &operator=(OwningFoo &&);
 private:
  std::size_t size_;
};

My second part of my question: Is this a good case for multiple and private inheritance? Am I shooting myself in the foot somewhere?

Note that if Foo is not a member, than std::unique_ptr can neither be a member, because it needs to be initilized before Foo.

Kristóf Marussy
  • 1,202
  • 8
  • 18
  • This is rather complicated and hard to follow IMHO. I think you need to keep it simple. The core issue is that sometimes you have data that is owned by your code, and other times it is owned by some external library, correct? So how about just a flag in `Foo` about who the owner is, and the destructor looks at the flag and deletes (or not) appropriately. – Nicu Stiurca Aug 22 '13 at 16:26
  • Well, I do not think that could work. An owning-`Foo` (or owner-of-`Foo`) is movable or copiable, while a non-owning-`Foo`, I think, shouldn't be so, lest it outlives the buffer it doesn't own. Due to the fact my code is already confusing for *mathematical* reasons (i.e. it is an implementation of a nontrivial mathematical model), I would like to check for as much correctness at compile time as it is practically possible. – Kristóf Marussy Aug 22 '13 at 16:32
  • 1
    If you want to check for mathematical correctness at compile time, go with a functional language. The C language was invented for the express purpose of writing operating systems, and C++, despite adding lots of fancy stuff like OOP, very much follows the same design principles. If you are doing math, use a language that is well suited for math, such as Haskell or Scala. You may lose some run-time performance, but you gain development-time efficiency and compile-time correctness checking. – Nicu Stiurca Aug 26 '13 at 13:51
  • Regarding non-owning Foo outliving a buffer it doesn't own, that's a good point. A partial fix would be to check the ownership flag I suggested not just at destruction time, but also at copy/move time, and possibly throw an exception. Obviously this isn't quite as good as a compile-time check, but considering how nasty your situation is to begin with, a clean solution may not be worth it. I'm sure you'd rather spend your time on the mathematics you are implementing/solving than on battling the language/compiler. – Nicu Stiurca Aug 26 '13 at 13:57

1 Answers1

1

The way I do it is push the ownership issue farther down. Foo has a buffer, and knows how to clean up that buffer when it is destroyed. The std::shared_ptr has a destroy callback that can be used for that purpose, for example. This shows the accepted pattern of having the smart pointer know how this particular instance is to be deleted.

Really, you should have a possibly shared buffer so the total ownership is tracked. Implicitly programming it to be "not me" with some other place knowing what's going on is rather brittle.

"checking ownership flags" is just a special case of "am I the last/only owner" which has a general robust implementation you can use.

In the twist you mention, how does the C code that owns the buffer coordinate with your class's lifetime? It sounds bad, and having your class know it doesn't own the buffer (in a well-encapsulated way) doesn't change the problem of the C code not knowing when your instance is done with it.

JDługosz
  • 5,592
  • 3
  • 24
  • 45
  • In the problem I was facing, I wanted to avoid any modification of the C library (in fact, a FORTRAN library referenced through `extern "C"`). While I did have the (open) source, the library did numerical optimization stuff I was unfamiliar with and afraid to break. However, my question is rather old and in happier situations, your approach is the preferable, so I mark it accepted. – Kristóf Marussy Sep 16 '14 at 14:20
  • 1
    Thanks. ’Tis true but often not taught to the extent that is needed in real life, that one has to deal with issues such as this. C++ is nearly unique in being able to do “anything you want” with respect to memory and interfacing, so it's well suited for such tasks. – JDługosz Sep 16 '14 at 23:18