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.