3

Why is using the assignment operator on a dereferenced pointer to a purely abstract interface not a compiler error? When could this ever be useful? It seems like it can only lead to subtle bugs as I've outlined below.

In order to have a globally accessible object, which implements an interface, with a default value, that I can later override, do I have to use a handle (pointer to a pointer -- or -- pointer to a std::unique_ptr), or is there another approach?

#include <iostream>

class Interface {
 public:
  virtual int Value() = 0;
};

class Implementation : public Interface {
 public:
  Implementation(int value) : value(value) {}
  int Value() override {
    return value;
  }
  int value;
};

// Set default value
Interface* object = new Implementation(1);
Implementation* object2 = new Implementation(2);

int main() {
    // Why is this line not a compiler error?
    *object = Implementation(3); // Try to override default value
    *object2 = Implementation(4); // Override default value
    // This outputs "14", not "34", why?
    std::cout << object->Value() << object2->Value();
    return 0;
}

To get around these issues, as well as allow setting the overriding the default value in unit tests, I have gone with the following approach.

// Set default value
static std::unique_ptr<Interface> object(new Implementation(1));
void SetObject(std::unique_ptr<Interface> obj) {
    object.reset(obj.release());
}

int main() {
    SetObject(std::make_unique<Implementation>(2)); // Override default value
    std::cout << object->Value();
    return 0;
}
AffluentOwl
  • 3,337
  • 5
  • 22
  • 32

1 Answers1

4

C++ objects by default get an operator= method generated by the compiler. This operator simply applies operator= to each of the member variables, and it is not virtual. That means it will work with the members of the static type of the declared pointer. Since Interface doesn't have any members, no members will be copied. Implementation has a member, so that member will be copied.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • I see. So the real problem was my claim that "Interface" was a "pure abstract interface". It actually isn't. In order for that to be the case, I would need to add a few more things like deleting the default functions in order for there to be the compiler error I would expect. "Interface& operator=(const Interface&) = delete;" – AffluentOwl May 17 '17 at 03:23
  • Is there a way to make the assignment operator on the interface virtual, and allow it to be overridden in such a way that I can get the final output of my main function to be "34"? – AffluentOwl May 17 '17 at 03:31
  • 1
    @AffluentOwl Add another virtual function to the class, something like `CopyAssign()` that subclasses can implement, then implement `operator=` to simply call that function – Justin May 17 '17 at 03:33
  • Mark, given your response, it isn't clear to me why this line works "Interface* object = new Implementation(1);". Is it not actually using the assignment operator? Because object should not gain the value of "1" if the assignment operator would only copy the fields from the Interface class. – AffluentOwl May 17 '17 at 03:36
  • 1
    @AffluentOwl assignment of a pointer (`object =`) is *not* the same as assignment of an object (`*object =`). – Mark Ransom May 17 '17 at 03:40
  • 1
    @AffluentOwl see http://stackoverflow.com/questions/1691007/whats-the-right-way-to-overload-operator-for-a-class-hierarchy for options to implement `operator=` on a class hierarchy. – Mark Ransom May 17 '17 at 03:42
  • I think the confusion came from not understanding that the Implementation class can have more than one assignment operator based upon the type on the RHS. `Implementation& operator=(const Interface& other) override` and `virtual Implementation& operator=(const Implementation& other)` – AffluentOwl May 17 '17 at 03:52