18

Have seen some related questions, but not this exact one...

I've treated classes as fitting into a few major categories, let's say these four for simplicity:

  • Value Classes which have some data and a bunch of operations. They can be copied and meaningfully compared for equality (with copies expected to be equal via ==). These pretty much always lack virtual methods.

  • Unique Classes whose instances have identity that you disable assignment and copying on. There's usually not an operator== on these because you compare them as pointers, not as objects. These quite often have a lot of virtual methods, as there isn't risk of since you're being forced to pass them by pointer or reference.

  • Unique-but-Clonable Classes which disable copying, but are pre-designed to support cloning if that's what you really want. These have virtual methods, most importantly those following the virtual construction / cloning idiom

  • Container Classes which inherit the properties of whatever they're holding. These tend not to have virtual methods...see for instance "Why don't STL containers have virtual destructors?".

Regardless of holding this informal belief system, a couple times I've tried adding a virtual method to something copyable. While I may have thought it would "be really cool if that worked", inevitably it breaks.

This led me to wonder if anyone has an actual good example of a type which has virtual methods and doesn't disable copying?

Community
  • 1
  • 1
  • You could say that a container class is "morally" a value class, it's just that if its contents aren't copyable then it fails to copy. I realise there's a fine distinction between saying that and saying what you did say, I just mean that it's not that containers in general inherit the category in this taxonomy, it's this one specific thing. – Steve Jessop Nov 04 '13 at 18:08
  • It isn't quite clear to me when you say virtual methods on something copyable do you mean your Unique-but-cloneable category or something else? Like your value category but with virtual functions. – stonemetal Nov 04 '13 at 21:23
  • @stonemetal Regardless of my categories, my question is at the end. I'm very literally asking whether **any** class with virtual methods would be better off if you made it non-copyable *(or alternatively changed to not use any virtual methods)*. – HostileFork says dont trust SE Nov 04 '13 at 23:40
  • 2
    It would be informative to tell how exactly "it breaks". – n. m. could be an AI Dec 01 '13 at 04:19
  • @n.m. Well, the term [tag:object-slicing] kind of gets to the core of it... the point is that you go making a copy and that copy doesn't act like the original... – HostileFork says dont trust SE Dec 01 '13 at 04:32

4 Answers4

7

The only counter-example that I have are classes that are meant to be stack-allocated and not heap-allocated. One scheme I use it for is Dependency Injection:

class LoggerInterface { public: virtual void log() = 0; };

class FileLogger final: public LoggerInterface { ... };

int main() {
    FileLogger logger("log.txt");

    callMethod(logger, ...);
}

The key point here is the final keyword though, it means that copying a FileLogger cannot lead to object-slicing.

However, it might just be that being final turned FileLogger into a Value class.

Note: I know, copying a logger seems weird...

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • I think you're right, `FileLogger` as written is a value class. And this is a good example of why that's not inconsistent with implementing a polymorphic interface. Just as long as the interface also disables copying by users (which it does, since it's an abstract class) you protect them from the obvious pitfall. I'm pretty sure you could come up with an example that's less weird to copy than a logger, we need for the copying to be a natural part of the concrete class interface but not the polymorphic interface. Which means an ideal example would be where that's obviously a good idea. – Steve Jessop Nov 04 '13 at 18:13
  • Ah - you see this more often in Java than in C++ but it's not unheard-of. Classes might implement some polymorphic listener interface so they can pass themselves into a notification mechanism they care about. A value type could want to do that regardless of the fact that it can be copied itself, although you might worry that it's a bit non-obvious whether or not the fact of being registered propagates to copies! – Steve Jessop Nov 04 '13 at 18:19
  • Thanks for the object-slicing term, hadn't heard that name for it before...(added the tag). I did actually wonder if using virtual methods for internal protocol might be useful, then setting it to final before it leaked out... although I couldn't think of a good example. Writing a value class that needs to conform to an interface looks like it could be one *(although this particular case does seem a bit confusing)*... – HostileFork says dont trust SE Nov 04 '13 at 18:27
  • @DieterLücking: I never said there was only one, or that there was only one level of inheritance (though it is what I presented, I admit). The emphasis is on the `final` which actually prevents further extension and thus somehow *removes* the virtuality of methods. – Matthieu M. Nov 05 '13 at 08:03
  • Why do you need the final keyword there? It will work without it. – BЈовић Dec 02 '13 at 07:39
  • @BЈовић: Yes, it will; however `final` emphasizes that `FileLogger` is a value class (cannot be derived from any longer). – Matthieu M. Dec 02 '13 at 09:25
6

There's nothing inherently wrong in being able to copy a polymorphic class. The problem is being able to copy a non-leaf class. Object slicing will get you.

A good rule of thumb to follow is never derive from a concrete class. This way, non-leaf classes are automatically non-instantiable and thus non-copyable. It won't hurt to disable assignment in them though, just to be on the safe side.

Of course nothing is wrong with copying an object via a virtual function. This kind of copying is safe.

Polymorphic classes are normally not "value-classes" but it does happen. std::stringstream comes to mind. It'not copyable, but it is movable (in C++11) and moving is no different from copying with regard to slicing.

Andriy Tylychko
  • 15,967
  • 6
  • 64
  • 112
n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243
  • 3
    And here are some support quotes to the rule of thumb: Herb Sutter: *“Don't derive from concrete classes.”* ([“Virtuality”](http://www.gotw.ca/publications/mill18.htm)). Scott Meyers: *“Make non-leaf classes abstract.”* (_More Effective C++_, Item 33). – gx_ Dec 01 '13 at 12:06
  • Does this by any chance align with saying that once you've provided implementations of all the abstract virtual functions in a class to the point it can be instantiated, it should become final if it supports copying (??) – HostileFork says dont trust SE Dec 06 '13 at 07:40
  • @HostileFork yes, it's the same principle. – n. m. could be an AI Dec 07 '13 at 07:42
3

Virtual dispatch happens a runtime. The only reason one should want it is when the actual, dynamic type of an object cannot be known until runtime. If you already knew the desired dynamic type when writing the program, you could use different, non-virtual techniques (such as templates, or non-polymorphic inheritance) to structure your code.

A good example for the need for runtime typing is parsing I/O messages, or handling events – any kind of situation where one way or another you'll either have some sort of big switch table to pick the correct concrete type, or you write your own registration-and-dispatch system, which basically reinvents polymorphism, or you just use virtual dispatch.

(Let me interject a warning: Many people misuse virtual functions to solve problems that don't need them, because they're not dynamic. Beware, and be critical of what you see.)

With this said, it's now clear that your code will be dealing mostly with the polymorphic base classes, e.g. in function interfaces or in containers. So let's rephrase the question: Should such a base class be copyable? Well, since you never have actual, most-derived base objects (i.e. the base class is essentially abstract), this isn't really an issue, and there's no need for this. You've already mentioned the "clone" idiom, which is the appropriate analogue of copying in a polymorphic.

Now, the "clone" function is necessarily implemented in every leaf class, and it necessarily requires copying of the leaf classes. So yes, every leaf class in a clonable hierarchy is a class with virtual functions and a copy constructor. And since the copy constructor of a derived class needs to copy its base subobjects, all the bases need to be copyable, too.

So, now I believe we've distilled the problem down to two possible cases: Either everything in your class hierarchy is completely uncopyable, or your hierarchy supports cloning, and thus by necessity every class in it is copyable.

So should a class with virtual functions have a copy constructor? Absolutely. (This answers your original question: when you integrate your class into a clonable, polymorphic hierarchy, you add virtual functions to it.)

Should you try to make a copy from a base reference? Probably not.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
  • To me “copyable type” implies _public_ copy-ctor (and copy-op=), but in a clonable hierarchy they can be made _non-public_ (not `private` (at least for non-leafs) since indeed the copy-ctor of a derived class needs access to its base's copy-ctor, but `protected`), which I think is what OP meant by “disable copying” (while supporting cloning). – gx_ Dec 04 '13 at 15:25
2

Not with a single, but with two classes:

#include <iostream>
#include <vector>
#include <stdexcept>

class Polymorph
{
    protected:
    class Implementation {
        public:
        virtual ~Implementation() {};
        // Postcondition: The result is allocated with new.
        // This base class throws std::logic error.
        virtual Implementation* duplicate() {
             throw std::logic_error("Duplication not supported.");
        }

        public:
        virtual const char* name() = 0;
    };

    // Precondition: self is allocated with new.
    Polymorph(Implementation* self)
    :   m_self(self)
    {}

    public:
    Polymorph(const Polymorph& other)
    :   m_self(other.m_self->duplicate())
    {}

    ~Polymorph() {
        delete m_self;
    }

    Polymorph& operator = (Polymorph other) {
        swap(other);
        return *this;
    }

    void swap(Polymorph& other) {
        std::swap(m_self, other.m_self);
    }

    const char* name() { return m_self->name(); }

    private:
    Implementation* m_self;
};

class A : public Polymorph
{
    protected:
    class Implementation : public Polymorph::Implementation
    {
        protected:
        Implementation* duplicate() {
            return new Implementation(*this);
        }

        public:
        const char* name() { return "A"; }
    };

    public:
    A()
    :   Polymorph(new Implementation())
    {}
};

class B : public Polymorph {
    protected:
    class Implementation : public Polymorph::Implementation {
        protected:
        Implementation* duplicate() {
            return new Implementation(*this);
        }

        public:
        const char* name() { return "B"; }
    };

    public:
    B()
    :   Polymorph(new Implementation())
    {}
};


int main() {
    std::vector<Polymorph> data;
    data.push_back(A());
    data.push_back(B());
    for(auto x: data)
        std::cout << x.name() << std::endl;
    return 0;
}

Note: In this example the objects are copied, always (you may implement shared semantics, though)

  • I'm not exactly following the example as the class with virtual methods (`Implementation`) seems to be following the cloning pattern, is passed by pointer, and uses swapping. Could you show a usage and how it wouldn't work if Implementation disabled copying? – HostileFork says dont trust SE Nov 04 '13 at 19:30
  • That `vector` example reminds me of Sean Parent's presentation “Value Semantics and Concepts-Based Polymorphism” =) Now, strictly speaking, `Polymorph` doesn't have virtual member functions, and `Implementation` could (should?) disable copying, so that's not really one class that “_has virtual methods and doesn't disable copying_”. But that's indeed “polymorphic value types” (i.e. *copyable concrete types* that exhibit *polymorphic behavior*). – gx_ Dec 01 '13 at 12:00