非实例化模板成员的编译时错误,而不是链接时错误
Compile-time error for non-instantiated template members instead of link-time error
>我有模板类ItemContainer
,它实际上是整个容器系列的外观,具有不同的功能,如排序、索引、分组等。
实现详细信息隐藏在文件中cpp.
使用 pimpl 习语和显式实例化。模板仅使用定义容器实际行为的已知有限实现类集进行实例化。
主模板实现了所有容器支持的通用函数 -IsEmpty()
、GetCount()
、Clear()
等。
每个特定的容器都专门处理一些仅受其支持的功能,例如Sort()
用于排序容器,operator[Key&]
用于关键索引容器等。
这种设计的原因是,该类取代了90世纪初一些史前学家编写的几种传统的手工自行车集装箱。我们的想法是用现代的STL&Boost容器取代旧的腐烂,尽可能保持旧的界面不变。
问题所在
当用户尝试从某些专用化调用不支持的函数时,这种设计会导致不愉快的情况。它编译正常,但在链接阶段产生错误(符号未定义)。 不是很用户友好的行为。
例:
SortedItemContainer sc;
sc.IsEmpty(); // OK
sc.Sort(); // OK
IndexedItemContainer ic;
ic.IsEmpty(); // OK
ic.Sort(); // Compiles OK, but linking fails
当然,通过使用继承而不是专用化可以完全避免它,但我不喜欢产生很多具有 1-3 个函数的类。想保持原始设计。
是否有可能将其转换为编译阶段错误而不是链接阶段一?我有一种感觉,静态断言可以以某种方式使用。
此代码的目标编译器是 VS2008,因此实际解决方案必须与 C++03 兼容,并且可以使用 MS 特定功能。 但便携式 C++11 解决方案也受到欢迎。
源代码:
// ItemContainer.h
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
template <class Impl> class ItemContainer
{
public:
// Common functions supported by all specializations
void Clear();
bool IsEmpty() const;
...
// Functions supported by sequenced specializations only
ItemPtr operator[](size_t i_index) const;
...
// Functions supported by indexed specializations only
ItemPtr operator[](const PrimaryKey& i_key) const;
...
// Functions supported by sorted specializations only
void Sort();
...
private:
boost::scoped_ptr<Impl> m_data; ///< Internal container implementation
}; // class ItemContainer
// Forward declarations for pimpl classes, they are defined in ItemContainer.cpp
struct SequencedImpl;
struct IndexedImpl;
struct SortedImpl;
// Typedefs for specializations that are explicitly instantiated
typedef ItemContainer<SequencedImpl> SequencedItemContainer;
typedef ItemContainer<IndexedImpl> IndexedItemContainer;
typedef ItemContainer<SortedImpl> SortedItemContainer;
// ItemContainer.cpp
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Implementation classes definition, skipped as non-relevant
struct SequencedImpl { ... };
struct IndexedImpl { ... };
struct SortedImpl { ... };
// Explicit instantiation of members of SequencedItemContainer
template void SequencedItemContainer::Clear(); // Common
template bool SequencedItemContainer::IsEmpty() const; // Common
template ItemPtr SequencedItemContainer::operator[](size_t i_index) const; // Specific
// Explicit instantiation of members of IndexedItemContainer
template void IndexedItemContainer::Clear(); // Common
template bool IndexedItemContainer::IsEmpty() const; // Common
template ItemPtr IndexedItemContainer::operator[](const PrimaryKey& i_key) const; // Specific
// Explicit instantiation of members of SortedItemContainer
template void SortedItemContainer::Clear(); // Common
template bool SortedItemContainer::IsEmpty() const; // Common
template void SortedItemContainer::Sort(); // Specific
// Common functions are implemented as main template members
template <class Impl> bool ItemContainer<Impl>::IsEmpty() const
{
return m_data->empty(); // Just sample
}
// Specialized functions are implemented as specialized members (partial specialization)
template <> void SortedItemContaner::Sort()
{
std::sort(m_data.begin(), m_data.end(), SortFunctor()); // Just sample
}
...
// etc
如果在编译时知道某个函数不会被实现,那么首先就不应该声明该函数。否则,这是一个编程错误。
话虽如此,您必须避免声明这样的函数,或者声明它,以便声明仅在实现时才有效。这可以通过static_assert
或SFINAE来实现。 例如
template<class Container> // you need one instantination per container supported
struct container_traits
{
static const bool has_sort; // define appropriately in instantinations
/* etc */
};
template<class container>
class ContainerWrapper {
unique_ptr<container> _m_container;
template<bool sorting> typename std::enable_if< sorting>::type
_m_sort()
{
_m_container->sort();
}
template<bool sorting> typename std::enable_if<!sorting>::type
_m_sort()
{
static_assert(0,"sort not supported");
}
public
void sort()
{
_m_sort<container_traits<container>::has_sort>();
}
/* etc */
};
考虑这个例子:
class A {
public:
void foo() {}
void bar();
};
只有在链接阶段,才能检测到未定义A::bar()
的错误,这与模板无关。
您应该为不同的容器定义单独的接口,并将它们用于您的实现。只是以下可能性之一:
template <class Impl>
class ItemContainerImpl
{
public:
ItemContainerImpl();
protected:
boost::scoped_ptr<Impl> m_data; ///< Internal container implementation
};
// No operations
template <class Impl>
class Empty : protected virtual ItemContainerImpl<Impl> {};
template <class Impl, template <class> class Access, template <class> class Extra = Empty>
class ItemContainer : public Extra<Impl>, public Access<Impl>
{
public:
// Common functions supported by all specializations
void Clear();
bool IsEmpty() const;
...
};
template <class Impl>
class SequencedSpecialization : protected virtual ItemContainerImpl<Impl> {
public:
// Functions supported by sequenced specializations only
ItemPtr operator[](size_t i_index) const;
...
};
template <class Impl>
class IndexedSpecialization : protected virtual ItemContainerImpl<Impl> {
public:
// Functions supported by indexed specializations only
ItemPtr operator[](const PrimaryKey& i_key) const;
...
};
template <class Impl>
class Sorted : protected virtual ItemContainerImpl<Impl> {
public:
// Functions supported by sorted specializations only
void Sort();
...
};
// Typedefs for specializations that are explicitly instantiated
typedef ItemContainer<SequencedImpl, SequencedSpecialization> SequencedItemContainer;
typedef ItemContainer<IndexedImpl, IndexedSpecialization> IndexedItemContainer;
typedef ItemContainer<SortedImpl, IndexedSpecialization, Sorted> SortedItemContainer;
尽管有很好的答案建议使用SFINAE,但我继续寻找符合我原始设计的解决方案。最后我找到了。
关键思想是对特定函数成员使用专用化,而不是显式实例化。
做了什么:
- 为主模板添加了特定函数虚拟实现。仅包含静态断言的实现放置在头文件中,但未内联到类定义中。
- 已从
.cpp
文件中删除了特定函数显式实例化。 - 特定函数专用化声明已添加到头文件中。
源代码:
// ItemContainer.h
//////////////////////////////////////////////////////////////////////////////
template <class Impl> class ItemContainer
{
public:
// Common functions supported by all specializations
void Clear();
bool IsEmpty() const;
...
// Functions supported by sorted specializations only
void Sort();
...
private:
boost::scoped_ptr<Impl> m_data; ///< Internal container implementation
}; // class ItemContainer
// Dummy implementation of specialized function for main template
template <class Impl> void ItemContainer<Impl>::Sort()
{
// This function is unsupported in calling specialization
BOOST_STATIC_ASSERT(false);
}
// Forward declarations for pimpl classes,
// they are defined in ItemContainer.cpp
struct SortedImpl;
// Typedefs for specializations that are explicitly instantiated
typedef ItemContainer<SortedImpl> SortedItemContainer;
// Forward declaration of specialized function member
template<> void CSortedOrderContainer::Sort();
// ItemContainer.cpp
//////////////////////////////////////////////////////////////////////////////
// Implementation classes definition, skipped as non-relevant
struct SortedImpl { ... };
// Explicit instantiation of common members of SortedItemContainer
template void SortedItemContainer::Clear();
template bool SortedItemContainer::IsEmpty() const;
// Common functions are implemented as main template members
template <class Impl> bool ItemContainer<Impl>::IsEmpty() const
{
return m_data->empty(); // Just sample
}
// Specialized functions are implemented as specialized members
// (partial specialization)
template <> void SortedItemContaner::Sort()
{
std::sort(m_data.begin(), m_data.end(), SortFunctor()); // Just sample
}
...
// etc
这样它至少适用于VS2008。
对于具有 C++11static_assert
的 GCC,使用需要一些技巧来启用惰性模板函数输入(编译示例):
template <class T> struct X
{
void f();
};
template<class T> void X<T>::f()
{
// Could not just use static_assert(false) - it will not compile.
// sizeof(T) == 0 is calculated only on template instantiation and
// doesn't produce immediate compilation error
static_assert(sizeof(T) == 0, "Not implemented");
}
template<> void X<int>::f()
{
std::cout << "X<int>::f() called" << std::endl;
}
int main()
{
X<int> a;
a.f(); // Compiles OK
X<double> b;
b.f(); // Compilation error - Not implemented!
}
这个呢?
template <class T, class supported_types> struct vec_enabler :
boost::mpl::contains<supported_types, T> {};
// adding Sort interface
template <class T, class enabler, class Enable = void>
struct sort_cap{};
template <class T, class enabler>
struct sort_cap<T, enabler,
typename boost::enable_if< typename enabler::type >::type>
{
void Sort();
};
// adding operator[]
template <class T, class U, class R, class enabler, class Enable = void>
struct index_cap{};
template <class T, class primary_key, class ret, class enabler>
struct index_cap<T, primary_key, ret, enabler,
typename boost::enable_if< typename enabler::type >::type>
{
ret operator[](primary_key i_index) const;
};
template <class Impl>
class ItemContainer :
public sort_cap<Impl,
vec_enabler<Impl, boost::mpl::vector<A, B> > >, // sort for classes A or B
public index_cap<Impl, size_t, ItemPtr,
vec_enabler<Impl, boost::mpl::vector<C> > >, // index for class C
public index_cap<Impl, primaryKey, ItemPtr,
vec_enabler<Impl, boost::mpl::vector<B> > > // index for class B
{
public:
void Clear();
bool IsEmpty() const;
};
我发现使用继承是实现您想要做的事情的最干净方法(即"向类添加接口")。然后我们有以下内容:
int main(){
ItemContainer<A> cA;
cA.Sort();
//ItemPtr p = cA[0]; // compile time error
ItemContainer<C> cC;
//cC.Sort(); // compile time error
ItemPtr p = cC[0];
//ItemPtr pp= cC[primaryKey()]; // compile time error
}
当然,您仍然可以在.cpp文件中编写实现。
- 静态数据成员的问题-修复链接错误会导致编译器错误
- Visual Studio mkl_link_tool.exe链接错误
- C++ 实现模板单例类时出现链接错误
- 使用类模板的方法链接错误
- MySQL C++连接器链接错误
- VS 2015 链接错误 无法构建依赖于 libcurl 的项目
- 运行 C++ 单元测试时LNK2005链接错误
- 对 CMake 中'cudaRegisterLinkedBinary'链接错误的未定义引用?
- 链接错误,包括我创建的相同头文件 - C++
- 在Embarcadero C++ Builder中生成的DLL未解决的外部链接错误
- 使用标头保护的多个定义链接错误
- 链接错误:未定义对 stdscr 和 wgetch 的引用
- 使用 Vivek 的 Vcam / 捕获源过滤器构建/链接错误
- 升压program_options中的链接错误
- 使用 g++ 预处理器进行替换会导致链接错误
- 在调试配置中编译工作正常,但发布会给出链接错误
- 相邻矩阵设置链接错误
- 包含常量变量并包含在多个文件中的标头的链接错误
- C++链接错误,我理解但无法解决
- 是什么导致macOS Mojave上的GoogleTest链接错误