views:

666

answers:

8

In my C++ header files I try to use forward declarations (class MyClass;) instead of #including the class header, as recommended in many C++ coding standards (the Google C++ Style Guide is one).

Unfortunately, when I introduce enumerations, I can't do the forward declaration any more. Like this:

//// myclass1.hpp ////

class MyClass1
{
    enum MyEnum1
    {
        Enum_A, Enum_B, Enum_C
    };
};

//// myclass2.hpp ////

// I want to avoid this
#include "myclass1.hpp"

// I'd prefer to do this (forward declaration)
class MyClass1;

class MyClass2
{
    // This is o.k.: I only need to forward declare MyClass1
    MyClass1* ptr;

    // This forces me to #include, but I don't want to!
    void func( MyClass1::MyEnum1 e );
};

The best solution I can think of so far is to replace enums with member constants:

//// myclass1.hpp  ////

MyClass1
{
    static const int Enum_A;
    static const int Enum_B;
    static const int Enum_C;
};

//// myclass1.cpp ////

const int Enum_A = 1;
const int Enum_B = 2;
const int Enum_C = 3;

In this case, though, the solution seems worse than the problem.

I'm currently looking through Large Scale C++ Software Design (Lakos) and Working Effectively with Legacy Code (Feathers) for dependency breaking techniques, but I haven't found a good solution yet.

+3  A: 

You can use forward declarations only when you are declaring a pointer. If you are declaring a non-pointer variable, you will have to include the relevant header file.

Since an enum variable is not a pointer you can't use forward declarations. And I don't think there's an alternative solution.

Frederick
There are other uses of forward declarations, as for example, declaring the signature of a function that takes that type as a parameter. It is just that enums cannot be forward declared: 'class X; X f( X*, X' compiles fine.
David Rodríguez - dribeas
+11  A: 

This is difficult to do nicely. Perhaps moving enums to a common header file would be a reasonable solution?

Edit: I know the question asked to avoid including a header file, but there's just no way (AFAIK) to do this. Moving enums to a separate header file at least minimises the amount of stuff in the header file you do need to include. It's certainly better than the craziness suggested in the question!

Dominic Rodger
+6  A: 

You cannot forward declare enum values - and your workaround is a step down the path to complete madness.

Are you experiencing any major compilation slowdowns caused by #including headers? If not, just #include them. Use of forward declarations is not "best practice" it is a hack.

anon
It is a good practice to forward declare (whenever possible) in headers. That reduces dependencies. If you use an internal class in the signature of your private members and not in public/protected methods then the user does not even need access to the header file that defines the type.
David Rodríguez - dribeas
We will have to disagree.
anon
... cont'd: think as an example of the PIMPL idiom. The whole idea is that the user of the class does not know how/what the internal class is. While that is a very concrete it is also quite graphic. I could work on other examples.
David Rodríguez - dribeas
Of course, not everyone does agree on every topic :)
David Rodríguez - dribeas
I'm with Neil. I don't want to reduce dependencies if they help me find compiler errors sooner than later.
Dave Van den Eynde
Neil, I don't understand your statement about forward decelerations being hacks.If I have a file in which I have a reference to SomeClass*, and I never dereference it, why would I ever want to include that class?If I use a forward declaration, I eliminate the dependency.If I accidentally dereference the pointer, the compiler will shout at me about "incomplete type" and I'll be able to fix it.What the problem with the concept of forward declarations?
Michael
+1  A: 

I don't think (I can be proven incorrect) that you can forward declare an internal type, nor an enumeration. You will need the definition of the enclosing class to use the enum.

While most style guides enforce not including unnecessary headers, in your case the header is necessary. Other options you can consider if you really want to avoid the inclusion would be defining the enumeration outside of the class and including the header that defines the enum.

David Rodríguez - dribeas
+1  A: 

You can use template arguments to program against 'general' enum types. Much like this:

// enum.h
struct MyClass1 { enum e { cE1, cE2, cELast }; };

// algo.h
// precondition: tEnum contains enumerate type e
template< typename tEnum > typename tEnum::e get_second() { 
    return static_cast<typename tEnum::e>(1); 
}

// myclass1.h

// myclass.h
template< typename tClass1 >
class MyClass2
{
    tClass1 * ptr;
    void func( tClass1::e e );
};
// main.cpp
#include "enum.h"
#include "algo.h"
int main(){ return get_second<Enum>(); }
xtofl
+3  A: 

C++0x's strongly typed enums can be forward declared. GCC 4.4.0 and CodeGear C++Builder 2009 support strongly typed enums.

There are a few enum-like classes floating around like the (proposed but never finalized and accepted) Boost.Enum available for download from the Boost Vault at this link. Since Boost.Enums are classes, they can be forward declared.

However, just putting enums in a separate file (as in this answer) seems the simplest, best solution (barring C++0x suport).

Josh Kelley
A: 

Forward declaration of enumerations has actually been proposed by the C++ standards committee. See this paper (pdf). It would certainly be a good feature!

E Dominique
A: 

If you are really running into compilation slowdowns because of header inclusion, the other option is to use an int instead of an enum. This is a rather unpopular approach since it degrades type safety. If you do take this approach, then I would also recommend adding code to programmatically do the bounds checking:

// in class1.h
class Class1 {
public:
    enum Blah {
       kFirstBlah, // this is always first
       eOne = kFirstBlah,
       ...
       kLastBlah // this is always last
    };
};

// in checks.h
#include <stdexcept>
namespace check {
template <typename T, typename U>
U bounds(U lower, T value, U upper) {
    U castValue = static_cast<U>(value);
    if (castValue < lower || castValue >= upper) {
        throw std::domain_error("check::bounds");
    }
    return castValue;
}
} // end check namespace

// in class2.h
class Class2 {
public:
    void func(int blah);
};

// in class2.cpp
#include "class2.h"
#include "class1.h"
#include "checks.h"

void Class2::func(int blah) {
    Class1::Blah blah_;
    blah_ = check::bounds(Class1::kFirstBlah, blah, Class1::kLastBlah);
}

It's not the prettiest solution, but it does solve the header dependency problem by moving some of the type safety that static compilation gives you into runtime code. I've use similar approaches in the past and found that a check namespace used in this way can make the resulting code almost as readable as enum based code with very little effort.

The caveat is that you do have to make an effort to write exception-safe code which I recommend regardless of whether you adopt this approach or not ;)

D.Shawley