我有这个简化的类(省略了许多细节):
template<class T, size_t nChunkSize = 1000>
class Holder
{
size_t m_nSize = 0;
size_t m_nChunkSize = nChunkSize;
public:
Holder(size_t nSize)
: m_nSize(nSize)
{
}
size_t GetChunkSize()
{
return m_nChunkSize;
}
T* GetChunk(size_t nChunkIndex)
{
// returns the address of the chunk nChunkIndex
return ###;
}
T& operator[](size_t nIndex)
{
// returns the element with index nIndex
return T();
}
};
我们的想法是拥有一个简单的内存管理器来分配大量的对象,但是如果没有足够的内存来将所有对象保存在一个地方,它就会将它们分块并封装所有内容。我知道我应该使用STL,但我有特定的理由这样做。
我希望为用户提供指定块大小的能力,并且能够获得指向特定块的指针,但前提是他们已经指定了模板参数,否则我希望在编译时禁用该功能。
我知道编译器应该知道 nChunkSize 是默认的还是用户指定的,但我是否可以获取该信息并使用它来删除 GetChunk 功能或使其成为用法不可编辑。
例如:
Holder<int, 200> intHolder(5000); // allocates 5000 integeres each chunk holds 200 of them
intHolder[312] = 2;
int* pChunk = intHolder.GetChunk(3); // OK, Compiles
Holder<int> intAnotherHolder(500); // allocates 500 but chunk size is hidden/implementation defined
pChunk = intAnotherHolder.GetChunk(20); // COMPILE ERROR
答案 0 :(得分:3)
您可以使用具有两个派生类的公共基类:一个专门用于提供size_t
的场景,另一个不提供一个派生类:
基础(基本上是你当前的课程):
template<typename T, size_t nChunkSize=1000>
class Base
{
size_t m_nSize = 0;
size_t m_nChunkSize = nChunkSize;
public:
Base(size_t nSize)
: m_nSize(nSize)
{
}
size_t GetChunkSize()
{
return m_nChunkSize;
}
T& operator[](size_t nIndex)
{
// returns the element with index nIndex
return T();
}
};
默认(无法拨打GetChunk
):
// empty argument list
template<typename T, size_t... ARGS>
class Holder : public Base<T>
{
static_assert(sizeof...(ARGS) == 0, "Cannot instantiate a Holder type with more than one size_t");
using Base<T>::Base;
};
Nondefaulted(有GetChunk
方法):
template<typename T, size_t nChunkSize>
class Holder<T, nChunkSize> : public Base<T, nChunkSize>
{
using Base<T>::Base;
public:
T* GetChunk(size_t nChunkIndex)
{
// returns the address of the chunk nChunkIndex
return nullptr;
}
};
答案 1 :(得分:2)
如果nChunkSize
是类型模板参数,您可以使用默认标记并根据该标记工作。由于它是非类型参数,您可以使用默认的标志值,然后在类定义中更正它:
template<class T, size_t nChunkSize = std::numeric_limits<size_t>::max()>
// flag value ^--------------------------------^
class Holder
{
size_t m_nSize = 0;
size_t m_nChunkSize =
nChunkSize == std::numeric_limits<size_t>::max() ? 1000 : nChunkSize;
//^If the flag value was used, correct it
T* GetChunk(size_t nChunkIndex)
{
//Check if the flag value was used
static_assert(nChunkSize != std::numeric_limits<size_t>::max(),
"Can't call GetChunk without providing a chunk size");
// return the address of the chunk nChunkIndex
}
如果没有传递默认参数,这将使GetChunk
无法编译。当然,如果您将最大size_t
传递给Holder
,那么它会默默地修复到1000
,但可能您并没有计划将值传递给高public class MyEntity
{
private readonly IValidatorFactory _validatorFactory;
public MyEntity(IValidatorFactory validatorFactory)
{
_validatorFactory = validatorFactory;
}
//Entity Properties Removed For Clarity
public void Validate()
{
if(_validatorFactory == null)
{
throw new ArgumentNullException("Validator Factory was null, cannot validate this item");
}
var validator = _validatorFactory.GetValidator(this.ItemTypeId);
valid = validator.Validate();
}
}
答案 2 :(得分:1)
我建议使用两个不同的类:如果他们预计会有不同的实现,为什么还要坚持一个单一的定义呢?
template<class T, size_t nChunkSize>
class ChunkHolder
{
size_t m_nSize = 0;
size_t m_nChunkSize = nChunkSize;
public:
ChunkHolder(size_t nSize) : m_nSize(nSize) {}
size_t GetChunkSize() { return m_nChunkSize; }
// returns the address of the chunk nChunkIndex
T* GetChunk(size_t nChunkIndex) { return nullptr; }
// returns the element with index nIndex
T& operator[](size_t nIndex) { return T(); }
};
template<class T>
class UnchunkHolder
{
size_t m_nSize = 0;
public:
UnchunkHolder(size_t nSize) : m_nSize(nSize) {}
// returns the address of the chunk nChunkIndex
T& operator[](size_t nIndex) { return T(); }
};
然后,我们定义辅助函数来创建一个类或另一个类:
template <typename T, size_t SIZE> ChunkHolder<T, SIZE>
Holder(size_t nSize) { return {nSize}; }
template <typename T> UnchunkHolder<T>
Holder(size_t nSize) { return {nSize}; }
最后,我们可以这样使用它:
auto x = Holder<int, 200u>(5000u);
auto y = Holder<int>(500u);
x
是一个Holder
1 ,具有chunk功能,y
缺少该功能,无法编译GetChunk
调用,只是因为底层类型缺乏该功能。
查看现场演示 here 。
ChunkHolder
,您可以使用通用实现(operator[]
,...
)创建基类或使用不同的类;这取决于您的实施需求。答案 3 :(得分:0)
基本上没有标准方法可以知道编译器是否添加了默认值,或者用户是否物理输入了它。当您的代码开始区分时,该值已经存在。
特定的编译器可以提供这样的钩子(例如__is_defaulted(nChunkSize)
之类的东西),仔细检查你的编译器文档可能有所帮助,但是常见的编译器似乎没有提供这样的功能。
我不确定用例的确切性质;但“通常”选项是使用部分模板专门化来区分实现,而不是真正关心nChunkSize
的值来自,而是而不是值是什么
#include <iostream>
using namespace std;
template <typename T, size_t nChunkSize = 1000>
struct A {
A() { cout << "A()" << endl; }
};
template <typename T>
struct A<T, 1000> {
A() { cout << "A() special" << endl; }
};
int main() {
A<int, 100> a1; // prints A()
A<int> a2; // prints A() special
return 0;
}
Demo sample。可以根据需要将更多常见细节移动到特征类或基类。
上面并没有达到你想要的效果。让您更接近目标职位的替代方案包括使用“特殊”值,该值可用于区分用户提供的值,或使用默认值。理想情况下,价值就是用户不太可能使用它;在这种情况下,我会想到0
。它仍然不能保证用户会使用0
,但在“合理的”客户端代码中不太可能有0
块大小。
template<class T, size_t nChunkSize = 0>
class Holder
{
size_t m_nSize = 0;
size_t m_nChunkSize = nChunkSize == 0 ? 1000 : nChunkSize;
// ...
然后可以使用 static_assert
来允许根据GetChunk
的值来编译nChunkSize
- 这是有效的,因为在编译时已知nChunkSize
T* GetChunk(size_t nChunkIndex)
{
static_assert(nChunkSize != 0, "Method call invalid without client chunk size");
// ...
缺点是GetChunk
在开发过程中仍然“可见”,但如果调用它,编译将失败。
你已经提到了最接近的,但在这里重复进行比较;是将类的实现推迟到某些BaseHolder
,然后将其与部分模板特化相结合,以确定客户端代码是否使用了块大小(nChunkSize
的值)。
template<typename T, size_t nChunkSize /*=1000*/>
// default not provided here, it is not needed
class BaseHolder
{
size_t m_nSize = 0;
size_t m_nChunkSize = nChunkSize;
// ...
};
template<typename T, size_t... ARGS>
class Holder : public Base<T, 1000>
{
// "default" value for nChunkSize required
// When sizeof...(ARGS) = 1, the specialisation is used
// when 0, the client has not provided the default
// when 2 or more, it is invalid usage
static_assert(sizeof...(ARGS) == 0, "Only 1 size allowed in the client code");
// ...
};
template<typename T, size_t nChunkSize>
class Holder<T, nChunkSize> : public Base<T, nChunkSize>
{
// non-default chunk size used (could still be 1000)
// includes the implementation of GetChunk
public:
T* GetChunk(size_t nChunkIndex)
{
// ...
}
};
这种方法的缺点是可以提供多个size_t
参数,这可以在编译时用static_assert
控制;代码的文档也应该清楚。