在我的班级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
中的任何其他必需项目展示给{ {1}}?以下是实践中的一个实例:
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
的实际定义以及私有实现的其他内部结构(例如Init()
),这会冒犯我的风格感和我'我想知道这是否真的像它一样好吗?我能说什么,C ++ 11非常干净,像这样的偏差似乎让我做错了。
因为这最初只是一个警告,似乎有一些语法可以用来推迟解决链接时间,但是由于使用了匿名命名空间并在这里使用了PIMPL惯用语,我不乐观。
感觉应该有一种方法可以显式实例化Impl
或任何其他需要内部链接的函数,而不会将定义暴露给Init()
。可能使用bar.cpp
和extern
的某种组合,但同样,匿名命名空间和template
的定义隐藏在Init()
中,这可能是唯一的方法。
我希望我错了,但遗憾的是不这么认为。