在我的类Foo
中,我需要使用辅助函数构造一个对象,并且 - 作为有助于强制代码结构的任意第二个函数 - 设置一个处理程序。
由于内部链接而暴露定义似乎是做到这一点的唯一方法,但它冒犯了我的风格,我希望我忽略了另一种实现这一点的方法。这是一个完整的例子来说明这一点(也包括makefile!):
// 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
哪个编译,一切都是肉汁,直到我尝试在外部使用它。
// 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;
}
当我尝试在没有 的情况下进行编译时ideal_bar.cpp
,-Werror
出现以下错误:
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
并且-Werror
,它抓住了这个的根源:
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
问题:避免这种情况的唯一方法是添加foo_impl.hpp
,从而将定义Init()
和任何其他必需项从Impl
to暴露出来bar.cpp
?这是实践中的一个工作示例:
// 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
然后返回fugly_bar.cpp
,#include "foo_impl.hpp"
例如:
// 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;
}
为了完整起见,这里是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
为了让事情变得简单,一个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 $@ $^
我需要使用一种模式来公开私有实现(例如)_impl.hpp
的实际定义和其他内部结构,这冒犯了我的风格,我想知道这是否真的和它一样好?我能说什么,C++11 真的很干净,像这样的偏差似乎我做错了什么。Init()
Impl
因为这最初只是一个警告,所以我似乎可以在这上面撒上一些语法来将解析延迟到链接时间,但是由于使用了匿名命名空间和此处使用 PIMPL 成语,我不乐观。
感觉应该有一种方法可以显式实例化Init()
或任何其他需要内部链接的函数,而无需将定义暴露给bar.cpp
. extern
也许使用and的某种组合template
,但同样,使用匿名命名空间和Init()
埋在的定义foo.cpp
,这可能是唯一的方法。
我希望我错了,但遗憾的是我不这么认为。