views:

105

answers:

4

My interface declarations usually (always?) follow the same scheme. Here's an example:

class Output
{
public:
    virtual ~Output() { }

    virtual void write( const std::vector<char> &data ) = 0;

protected:
    Output() { }

private:
    Output( const Output &rhs ); // intentionally not implemented
    void operator=( const Output &other );  // intentionally not implemented
};

The boilerplate is always the same: public virtual destructor, a few pure virtual methods which make up the actual interface. protected default ctor, disabled copy construction and copy assignment. I started using two little helper macros which can be used to simplify the above to

ABSTRACT_BASECLASS_BEGIN(Output)
    virtual void write( const std::vector<char> &data ) = 0;
ABSTRACT_BASECLASS_END(Output)

Unfortunately, I didn't find a nice way to do this with just a single macro. Even better, I'd like to avoid macros entirely. However, the only thing which came to my mind was a code generator, which is a bit overkill for me.

What is the simplest way to declare an interface in C++ - directly in the language. Preprocessor use is acceptable, but I'd like to avoid external code generators.

+2  A: 

how about something of this sort:

ABSTRACT_BASECLASS(Output,
    (virtual void write( const std::vector<char> &data ) = 0;)
    (more methods))

(first)(second)(...) is a boost preprocessor sequence, which is really single argument as far as your macro is concerned: http://www.boost.org/doc/libs/1_43_0/libs/preprocessor/doc/index.html

#define BASECLASS(name, methods) \
...\
BOOST_PP_SEQ_CAT(methods)\  //will concatenate methods declarations
...\
aaa
Okay, now *that* looks like lisp.
SigTerm
+1: This won't win beauty contests, but thanks for trying to answer my question directly instead of trying to rephrase the question. ;-)
Frerich Raabe
+3  A: 

Consider using a base class:

class NoncopyableBase
{
public:
    NoncopyableBase() { }
    virtual ~NoncopyableBase() { }
private:
    NoncopyableBase(const NoncopyableBase&);
    void operator=(const NoncopyableBase&);
};

Any class derived from this base class will be noncopyable and will have a virtual destructor.

James McNellis
I don't know about gcc, but with this code vc10 says the misuse is on the derived class, not the point of the actual copy.
Simon Buchan
Good answer; I saw this idiom (inheriting from a class as you described it) in a few places. My only gripe with this is that it feels a bit hacky to use inheritance to save a little typing work. After all, I don't have a function `void f( NoncopyableBase *o )` which expects to access `NoncopyableBase` objects polymorphically.
Frerich Raabe
+3  A: 

Personally, I would remove the copy constructor declaration. You have pure virtual functions so instances of this class can't be created by slicing in any case. If a derived class isn't copyable because of the nature of a resource that it holds; it can mark itself as non-copyable.

Then I would get rid of the protected default constructor; it doesn't do anything and you have to derive from this class in any case as you have pure virtual functions so it's not preventing any usage.

Although marking the copy assignment operator prevents someone doing *pBase1 = *pBase2 (effectively a no-op), personally I'm not convinced that it's worth the bother of trying to prevent it. As your class has no data members there's no inherent danger in the compiler generated one, it's really up to user to use pointers or references to base classes appropriately.

I would just go with:

class Output
{
public:
    virtual ~Output() {}
    virtual void write( const std::vector<char> &data ) = 0;
};
Charles Bailey
or `struct Output { virtual ~Output() {} virtual void write(); }` of course.
Simon Buchan
+1: Thank you for your reasoning. Your answer makes perfect sense. However, there are some sad little cases in our code base where an interface is not as pure as I'd like. People snug member variables between the `START`/`END` macros, in a few places and there are also ugly ducklings whose methods are virtual, but not pure virtual. It's for those cases the the copy constructor declaration (and hence, the default constructor declaration) is needed.
Frerich Raabe
The reason for the copy assignment operator to be private is that I want the compiler to complain in case people attempt to run `*pBase1 = *pBase2;`. As you correctly point out, doing so should be a no-op. However, it might be an indication of a defect in the code in case somebody tries to do something like that. Maybe a brain fault of the develoeper or something. So instead of going for "don't bother, go on", I'd like to have "make the compiler yell" in case people do calls like that which just don't make any sense.
Frerich Raabe
+2  A: 

unfortunately, I didn't find a nice way to do this with just a single macro.

IMO, the easiest solution is to put default methods into macro. Like this:

#include <vector>

#define DEFAULT_CLASS_METHODS(C) public: \
        virtual ~C(){}; \
    protected: \
        C(){}; \
    private: \
        inline C(const C& rhs){}; \
        inline void operator=(const C& other){}; 

class Output{
    DEFAULT_CLASS_METHODS(Output)
public:
    virtual void write(const std::vector<char> &data) = 0;
};

This way you'll need only one macro per class definition in *.h file. You will need additional macro to actually declare copy constructor and assignment operator in some *.cpp file or you could make default copy constructor and assignment operators inline, which will wrap everything into single macro.

The only problem is that you'll need to type class name twice.

There is a less elegant way to do that without typing class name twice:

#include <vector>

#define BEGIN_INTERFACE(C) class C{ \
    public: \
        virtual ~C(){}; \
    protected: \
        C(){}; \
    private: \
        inline C(const C& rhs){}; \
        inline void operator=(const C& other){}; 

BEGIN_INTERFACE(Output)
public:
    virtual void write(const std::vector<char> &data) = 0;
};

As you can see, in this case macro eats opening { bracket, which will be very misleading.

SigTerm
+1: I like the first version! If it wasn't for Charles' answer (which I have to think about a bit), I would probably accept this answer. the second version is indeed a bit more compact but unfortunately the missing `{` confuses vim too much to be acceptable for me. :-}
Frerich Raabe