views:

221

answers:

5

Setup:

class A {
  public:
    void a() {}
};

class B {
  public:
    void b() {}
};

class C: public A, public B {
  public:
    void c() {}
};

What (I thought) I should be able to do:

C* foo = new C();
foo->b();

And I get the following linker error from GCC:

`... undefined reference to 'C::b(void)'`

If I use explicit scope resolution it works, as in:

C* foo = new C();
foo->B::b();

A, B, and C share no members with similar signatures, so I know nothing is being hidden. Two questions:

1) Why, theoretically, am I not able to access public base class members implicitly?

2) What (if anything) in practice can I do to avoid this annoying syntax?

I can continue development with the explicit syntax (although I'd rather be rid of it) but I'm more looking to learn something here about my (apparently incorrect) knowledge of public inheritance in C++.

Cheers!

EDIT: Sorry, updated the sample code - some bad mistypes in my original.

EDIT2: Here's the (relevant parts of) the real code: from src/creature.h:

#include "container.h"
#include "identifiers.h"

class Creature: public Identifiers, public Container {
  public:
    Creature( void );
    Creature( const Creature& ref );
    virtual ~Creature( void );
};

from src/identifier.h:

class Identifiers {
  public:
    Identifiers( void );
    Identifiers( const Identifiers& ref );
    ~Identifiers( void );
};

from src/container.h:

class Container {
  public:
    Container( std::string (Object::*getName)( void ) const );
    Container( const Container& ref );
    ~Container( void );

    void  add( Object* object );
    void  add( const std::list<Object*>& objects );
    void  remove( Object* object );
    void  remove( const std::list<Object*>& objects );
};

from src/container.cpp:

Container::Container( std::string (Object::*getName)( void ) const ) {
  _getName = getName;
  return;
}

Container::Container( const Container& ref ) {
  _getName = ref._getName;
  return;
}

Container::~Container( void ) {
  while ( !objectList().empty() ) {
    delete objectList().front();
    objectList().pop_front();
  }
  return;
}

void Container::add( Object* object ) {
  objectList().push_back( object );
  return;
}

void Container::add( const std::list<Object*>& objects ) {
  objectList().insert( objectList().end(), objects.begin(), objects.end() );
  return;
}

void Container::remove( Object* object ) {
  objectList().remove( object );
  return;
}

void Container::remove( const std::list<Object*>& objects ) {
  for ( std::list<Object*>::const_iterator it = objects.begin(); it != objects.end(); ++it ) {
    remove( *it );
  }
  return;
}

from the application itself:

bool CmdWear::execute( Creature* creature, const std::string& args ) {
  std::list<Object*> objects;
  Object* removed = NULL;
  std::string error;

  objects = creature->searchObjects( args );

  for ( std::list<Object*>::iterator it = objects.begin(); it != objects.end(); ++it ) {
    if ( creature->wear( *it, error, removed ) ) {
      creature->remove( *it );
    }
  }

  return true;
}

Notes:

1) All methods shown here are defined in their appropriate .cpp file.

2) Each associated object file compiles just fine. Only the linker fails at the last step of linking all the object files together.

3) GCC spits out: undefined reference to `Creature::remove(Object*)' unless I qualify explicitly the scope by saying:

creature->Container::remove( *it );

Makefile:

PROJECT     = symphony
CPPC        = g++
FLAGS_DEV   = -ggdb3 -ansi -Wall -Werror -pedantic-errors
LIBS        = `pcre-config --libs` `mysql_config --libs`
SRC_DIR     = src
OBJ_DIR     = obj
BIN_DIR     = .
SRC_FILES  := $(wildcard $(SRC_DIR)/*.cpp)
OBJ_FILES  := $(patsubst src/%.cpp,obj/%.o,$(SRC_FILES))

dev: $(OBJ_FILES)
  $(CPPC) $(LIBS) $(FLAGS_DEV) $(OBJ_FILES) -o $(BIN_DIR)/$(PROJECT)

$(OBJ_DIR)/%.o: $(SRC_DIR)/%.cpp
  $(CPPC) -c $(FLAGS_DEV) $< -o $@
+4  A: 

You can't call a function that's not defined. gcc's telling you exactly that. And foo::B->b(); is not valid C++. It doesn't compile with either g++ or cl.exe.

If you wrote

public:
  void b() {}

it would work.

Alex
But C inherits publicly from A and B, and B does define b(void). And this compiles on GCC 4.4.1 with the -ansi flag.
Chris
B doesn't define b(void). It only declares it.
Alex
Sorry - those methods are defined. Thanks for the heads up.
Chris
@Chris, there is a difference between declaration and definition. B::b() is **declared** but it is not **defined**. Also, the correct syntax would be `foo->B::b()` and that shouldn't help you one bit, because B::b() still isn't defined anywhere.
Dan
It might be valid if `B` was a static member of `foo` with a pointer type... I don't think we've been given all the code.
Charles Bailey
Indeed, or if `foo` was a namespace type and `B` was a pointer to some class with a public member function `b()`.I guess I meant that *given the context*, it is not valid.
Alex
+1  A: 
C foo = new C();
foo->b();

This is wrong, you probably wanted:

C foo;
foo.b();

it should work, as long as that error you're getting is not a linker error (I'm not familiar with the gcc suite). If it's a linker error it means your function doesn't have a body, so there's nothing to call.

Either will work. Though stack-allocation is preferred, this is unrelated to the problem.
GMan
No, C foo = new C(); won't work because new C() will return a pointer. In his example the definition of foo is wrong. C * foo = new C(); would work tho
A: 
Try this out :

C *foo = new C();

And where is the function definitions ??.

Explicit scope resolution is only necessary in this case:

class A {
 public:
     void fun(){};
};

class B {
 public:
     void fun(){};
};

class C : public A , public B
{

};

int main()
{

   C c;

   c.A::fun();

}
Ashish
+1  A: 

I don't think that either of the snippets that you've posted should compile.

class C: public A, public B {
  public
    void c();
};

You need a : after the public access specifier.

C foo = new C();
foo->b();

You are trying to initialized a C from a pointer to dynamically allocated C object. . not -> is the correct operator to call a function on an object type. -> is for pointer types (or objects with an overloaded -> operator) only.

You need something like:

C foo;
foo.b();

I'm not sure how the snippet that you say works actually compiles.

Charles Bailey
+1  A: 

The error you get is a linker error, not a compiler error. That implies that your code compiles -- good. The linker is complaining because it can't find the definition of the function. Is the definition in a different C++ file? Perhaps there is a file B.cpp which contains:

#include "B.h"
void B::b() {
  // do something nifty here
}

and then there is a separate file C.cpp which contains:

#include "C.h"
C *
func() {
  C* foo = new C();
  foo->b();
  return foo;
}

If you only compile C.cpp, such as:

g++ -Wall -Wextra C.cpp

Then you will get the linker error you see.

Perhaps you want to do a partial compilation and then link all the .o files later?

g++ -Wall -Wextra -c C.cpp

Or perhaps you want to compile them all together and link at the same time?

g++ -Wall -Wextra A.cpp B.cpp C.cpp
Dan