glumesc

December 18, 2009

In which our hero learns about forward declaration

Filed under: Development — Tags: , , — steve @ 5:12 pm

And so, the latest step up the steep learning curve that accompanies C++ is completed. Let’s start with some code.

The following is a heavily simplified version of a CLI wrapper that I am writing to expose some unmanaged C++ libraries to C#:

Elephant.h: Elephant.cc:

#pragma once

#include "UnmanagedElephant.hh"
#include "Water.h"

namespace Jungle
{
    public ref class Elephant
    {
        public:        
            Elephant();

            ~Elephant();

            Water^ GetWater();
    };
}}}}


#include "Elephant.h"

using namespace Jungle;

Elephant::Elephant()
{
    /* code */
}

~Elephant::Elephant()
{
    /* code */
}

Water^ Elephant::GetWater()
{
    return gcnew Water();
}

Water.h: Water.cc:

#pragma once

#include "UnmanagedWater.hh"
#include "Fish.h"

namespace Jungle
{
    public ref class Water
    {
        public:        
            Water();

            ~Water();

            Fish^ GetFish();
    };
}}}}


#include "Water.h"

using namespace Jungle;

Water::Water()
{
    /* code */
}

~Water::Water()
{
    /* code */
}

Fish^ Water::GetFish()
{
    return gcnew Fish();
}

Fish.h: Fish.cc:

#pragma once

#include "UnmanagedFish.hh"
#include "Water.h"

namespace Jungle
{
    public ref class Fish
    {
        public:        
            Fish();

            ~Fish();

            Water^ GetWater();
    };
}}}}


#include "Fish.h"

using namespace Jungle;

Fish::Fish()
{
    /* code */
}

~Fish::Fish()
{
    /* code */
}

Water^ Fish::GetWater()
{
    return gcnew Water();
}

The first build error this produced was the following:

error C2512: no appropriate default constructor available

This was produced at the Water^ GetWater(); line of Elephant.h.

After much poking around the web, I stumbled across the page mentioned in my previous post. This pointed me to the need to add #ifndef/#define/#endif flags around the code to prevent header files being included multiple times while compiling a source file (I had previously assumed the “#pragma once” statement was sufficient).

After adding these flags to the header files so they looked like this…

Elephant.h:

#pragma once

#ifndef INC_ELEPHANT_HPP
#define INC_ELEPHANT_HPP

#include "UnmanagedElephant.hh"
#include "Water.h"

namespace Jungle
{
    public ref class Elephant
    {
        public:        
            Elephant();

            ~Elephant();

            Water^ GetWater();
    };
}}}}

#endif

…the error disappeared to be replaced by the following:

error C2143: syntax error : missing ';' before '^'

Again, this was produced at the Water^ GetWater(); line of Elephant.h.

So, back to the web. Unfortunately this is an exceptionally vague error that basically means the compiler got confused for some reason and the line in question is the last line it encountered before it all went pear shaped. This took a *long* time to figure out.

More by luck than judgement, I finally staggered across the following post: http://www.codeguru.com/forum/showthread.php?t=383253 . This highlighted the fact that circular dependencies among includes is a very bad thing. The solution is to use “forward declaration”, which essentially means adding a reference to the class to the header file and adding the include statement for that class to the source file.

In my case, the problem was the fact that Elephant included Water which included Fish which included Water. The inclusion of Water’s headers twice caused the compiler to get its knickers in a twist.

The final solution looks like this:

Fish.h: Fish.cc:

#pragma once

#include "UnmanagedFish.hh"

namespace Jungle
{
    ref class Water;

    public ref class Fish
    {
        public:        
            Fish();

            ~Fish();

            Water^ GetWater();
    };
}}}}


#include "Fish.h"
#include "Water.h"

using namespace Jungle;

Fish::Fish()
{
    /* code */
}

~Fish::Fish()
{
    /* code */
}

Water^ Fish::GetWater()
{
    return gcnew Water();
}

Here endeth the lesson.

C++ Basics: How include statements work

Filed under: Development — Tags: , — steve @ 10:24 am

Nice discussion/explanation of how #include statements work in C++, including tricks to avoid common problems:

http://bytes.com/topic/c/answers/132010-how-do-header-files-work

Theme: Silver is the New Black. Blog at WordPress.com.

Follow

Get every new post delivered to your Inbox.