我正在寻找一种方法来扩展int
变量的某些状态信息,这种信息很少使用,而且大部分时间只是重要的int值。这就是为什么我想避免使用带有其他布尔成员的结构。
相关的附加属性最多可以有5到6个附加属性,例如NULL,UNDEFINED,NAN,MISSING,其中只有一个属实。
想法:
int指针是为一个附加属性执行此操作的一种方法:nullptr是int变量可以采用的另一个值。但在这种情况下,我没有更多的例外状态。
另一种选择是使用一些我不希望使用的魔术值,例如UNDEFINED = std::numeric_limits<int>::min()
和MISSING = std::numeric_limits<int>::min()+1
等等。
问题:有没有更好的方法(这需要最少的额外内存)?
子问题:如果我可以在编译期间判断该值是否属于例外情况,是否有一种不使用额外内存的好方法?
答案 0 :(得分:1)
最好的替代选择不是使用整数,而是使用其他类型 - 您使用int*
的建议也属于此类别。这种包装器类型当然可以使各种操作符超载以便于访问底层整数值。定义一个新类型(或利用现有的标准库类型)是最佳的内存条件,因为你可能只会将每个整数的大小加倍(例如,现在你可能有整数+枚举(由int或更小的后盾)+可能的填充)。根据您的整数大小,即使指针也可能有更多的内存开销。
如果只有少数几个整数具有额外状态,并且这些整数不会被复制或移动,那么您可以将外部状态存储在从整数标识到状态的映射中。即我们使用指向整数对象的指针作为键。对于具有额外状态的每个整数,此映射将具有比备选方案高得多的内存开销,但根据您的使用模式,这可能是最紧凑的解决方案。显然,这里存在内存泄漏的机会,因此您应该将整数包装在一个自定义类型中,以便在销毁时删除任何映射条目。大概是这样的:
enum class IntStatus { IS_NAN, IS_MISSING };
class IntWithExternalStatus {
public:
explicit IntWithExternalStatus(int x = 0) : m_value{x} {}
explicit IntWithExternalStatus(IntStatus s) : m_value{} { s_status.insert({this, s}); }
~IntWithExternalStatus() { m_status.erase(this); }
operator int& () { return m_value; }
operator int () const { return m_value; }
bool is_valid() const { return s_status.find(this) == s_status.end(); }
bool is_nan() const {
auto it = s_status.find(this);
return it != s_status.end() && it->second == IntStatus::IS_NAN;
}
bool is_missing() const {
auto it = s_status.find(this);
return it != s_status.end() && it->second == IntStatus::IS_MISSING;
}
private:
static std::unordered_map<IntWithExternalStatus const*, IntStatus> s_status;
int m_value;
};
也许所有这些额外类型都是不必要的过度复杂。如果您只有一些需要额外状态的整数变量,则为状态创建单独的变量可能是最简单的。 E.g:
int m_foo;
int m_bar;
IntStatus m_foo_status;
IntStatus m_bar_status;
由于对齐问题,这可能会导致内存布局比定义组合的int-and-status对象更紧凑。
为状态使用特殊值是一个简单的解决方案,没有空间开销,但有一个巨大的缺点:对这些整数的任何算术都将擦除状态并产生虚假值。您将需要大量的运行时检查来防止这种情况,最好将其封装在一个单独的类型中。
关于编译时子问题,这取决于您对内存使用的定义。例如。您可以使用模板元编程,根据编译时值选择int
或InvalidInt
类型,其中InvalidInt是一种空对象模式。但是,这将为所有实例化模板生成专用代码。特别是,使用status-ints的代码也必须是模板化的。如果你有很多status-ints同时具有相同的状态,这可能会减少总内存使用,但在其他情况下不太可能有用。