0

在我的类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()和任何其他必需项从Implto暴露出来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,这可能是唯一的方法。

我希望我错了,但遗憾的是我不这么认为。

4

0 回答 0