0

In my class Foo, I need to construct an object using a helper function, and - as an arbitrary second function that helps force the structure of code - set a handler.

Exposing the definition due to internal linkage seems like the only way to do this, but it offends my sense of style and I'm hoping I've overlooked another way of pulling this off. Here is a complete example that illustrates the point (makefile included, too!):

// ideal_foo.hpp
#ifndef IDEAL_FOO_HPP
#define IDEAL_FOO_HPP

#include <functional>
#include <memory>
#include <string>

namespace ideal {

template <typename T>
class Foo : public std::enable_shared_from_this< Foo<T> > {
 public:
  using FooType = Foo<T>;
  using HandlerFunc = std::function<void(const std::string&)>;
  using Ptr = std::shared_ptr<FooType>;

  static Ptr Init();
  ~Foo() = default;
  void setHandler(HandlerFunc func);

 private:
  Foo();
  class Impl;
  std::unique_ptr<Impl> impl_;
};

} // namespace ideal
#endif // IDEAL_FOO_HPP
// ideal_foo.cpp
#include "ideal_foo.hpp"

namespace ideal {

template<typename T>
class Foo<T>::Impl {
 public:
  HandlerFunc func_ = nullptr;
};

template<typename T>
typename Foo<T>::Ptr
Foo<T>::Init() {
  return std::make_shared<Foo>();
}

template<typename T>
Foo<T>::Foo() : impl_{new Impl{}} { }

template<typename T>
    void
Foo<T>::setHandler(HandlerFunc func) {
  impl_->func_ = func;
}

} // namespace ideal

Which compiles, and everything is gravy, until I try and use it externally.

// ideal_bar.cpp
#include "ideal_foo.hpp"

namespace ideal {
namespace {

class Bar {
 public:
  using FooType = Foo<Bar>;
  Bar() : foo_{FooType::Init()} {}

  FooType::Ptr foo_;
};

} // unnamed-namespace
} // namespace ideal


int
main() {
  ideal::Bar b;
}

And when I attempt to compile ideal_bar.cpp without -Werror, I get the following error:

clang++ -std=c++11 -stdlib=libc++ -Wall -pedantic -o ideal ideal_bar.o ideal_foo.o
Undefined symbols for architecture x86_64:
  "ideal::Foo<ideal::(anonymous namespace)::Bar>::Init()", referenced from:
      ideal::(anonymous namespace)::Bar::Bar() in ideal_bar.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [ideal] Error 1

and with -Werror, it catches the root of this:

clang++ -std=c++11 -stdlib=libc++ -Wall -Werror -pedantic -c -o ideal_foo.o ideal_foo.cpp
clang++ -std=c++11 -stdlib=libc++ -Wall -Werror -pedantic -o ideal ideal_bar.o ideal_foo.o
Undefined symbols for architecture x86_64:
  "ideal::Foo<ideal::(anonymous namespace)::Bar>::Init()", referenced from:
      ideal::(anonymous namespace)::Bar::Bar() in ideal_bar.o
ld: symbol(s) not found for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)
make: *** [ideal] Error 1

Question: Is the only way to avoid this to add foo_impl.hpp, thereby exposing the definitions of Init() and whatever other required items from Impl to bar.cpp? Here's a working example of this in practice:

// fugly_foo.hpp
#ifndef FUGLY_FOO_HPP
#define FUGLY_FOO_HPP

#include <functional>
#include <memory>
#include <string>

namespace fugly {

template <typename T>
class Foo : public std::enable_shared_from_this< Foo<T> > {
 public:
  using FooType = Foo<T>;
  using HandlerFunc = std::function<void(const std::string&)>;
  using Ptr = std::shared_ptr<FooType>;

  static Ptr Init();
  ~Foo() = default;
  void setHandler(HandlerFunc func);

 private:
  Foo();
  class Impl;
  std::unique_ptr<Impl> impl_;
};

} // namespace fugly
#endif // FUGLY_FOO_HPP

And then back in fugly_bar.cpp, #include "foo_impl.hpp" like:

// fugly_bar.cpp - bar.cpp, the fugly revised edition
#include "fugly_foo.hpp"

namespace fugly {
namespace {
class Bar;
} // unnamed-namespace
} // namespace fugly

// FUGLY: Is this really the only way to get this to work?
#include "fugly_foo_impl.hpp"

namespace fugly {
namespace {

class Bar {
 public:
  using FooType = Foo<Bar>;
  Bar() : foo_{FooType::Init()} {}

  FooType::Ptr foo_;
};

} // unnamed-namespace
} // namespace fugly

int
main() {
  fugly::Bar b;
}

For completeness, here's fugly_foo.cpp:

// fugly_foo.cpp
#include "fugly_foo.hpp"

namespace fugly {

template<typename T>
class Foo<T>::Impl {
 public:
  HandlerFunc func_ = nullptr;
};

template<typename T>
typename Foo<T>::Ptr
Foo<T>::Init() {
  return std::make_shared<Foo>();
}

template<typename T>
    void
Foo<T>::setHandler(HandlerFunc func) {
  impl_->func_ = func;
}

} // namespace fugly

fugly_foo_impl.hpp:

#ifndef FUGLY_FOO_IMPL_HPP
#define FUGLY_FOO_IMPL_HPP

#include <memory>

#include "fugly_foo.hpp"

namespace fugly {

template<typename T>
class Foo<T>::Impl {
 public:
  HandlerFunc func_ = nullptr;
};

template<typename T>
typename Foo<T>::Ptr
Foo<T>::Init() {
  return Ptr(new Foo);
}

template<typename T>
Foo<T>::Foo() : impl_{new Impl{}} { }

} // namespace fugly

#endif // FUGLY_FOO_IMPL_HPP

and to make things easy, a GNUmakefile:

CXX=clang++
CXXFLAGS=-std=c++11 -stdlib=libc++ -Wall -Werror -pedantic
SRCS=idal_bar.cpp ideal_foo.cpp fugly_bar.cpp fugly_foo.cpp

%.o : %.cpp %.hpp
    ${CXX} ${CXXFLAGS} -c -o $@ $<

all: fugly ideal

clean::
    rm -rf $(SRCS:.cpp=.o) fugly ideal

fugly: fugly_bar.o fugly_foo.o
    ${CXX} ${CXXFLAGS} -o $@ $^

ideal: ideal_bar.o ideal_foo.o
    ${CXX} ${CXXFLAGS} -o $@ $^

That I need to use an _impl.hpp pattern that exposes the actual definition of Init() and other internals of the private implementation (e.g. Impl), offends my sense of style and I'm wondering if this is really as good as it gets? What can I say, C++11 is really clean and deviations like this seem like I'm doing something wrong.

Because this shows up initially as just a warning, it seems like there is a bit of syntax that I could sprinkle on this to defer resolution to link time, but because of the use of anonymous namespaces and using the PIMPL idiom here, I'm not optimistic.

It feels like there should be a way to explicitly instantiate Init() or any other function requiring internal linkage without exposing the definition to bar.cpp. Maybe using some combination of extern and template, but again, with the anonymous namespace and definition of Init() buried in foo.cpp, this may be the only way to do it.

I'm hoping I'm wrong, but sadly don't think so.

Sean
  • 9,888
  • 4
  • 40
  • 43
  • Which compiler are you using? With g++ 4.7.3, I had to replace std::enable_shared_from_this by std::enable_shared_from_this > and get rid of the "typename" in Foo and ~Foo definitions to get it to compile. Then, foo.cpp and bar.cpp compiled without any issue, without the need for a foo_impl. – small_duck Jul 13 '13 at 08:52
  • The above examples have been updated. If you pull out `-Werror`, it will compile, but will fail miserably at link time. – Sean Jul 13 '13 at 17:39
  • 1
    I've tried to simplify your example. Eventually, it seems to me that it all comes down to the definitions of the methods from your template being in the cpp file, and so unavailable to Bar to expand the template. I systematically put my template definitions inline with the template, other people include a ".inc" at the bottom of the header file with the definitions. – small_duck Jul 13 '13 at 21:02

0 Answers0