13

我见过/使用的许多库都有 typedef 来提供可移植的、固定大小的变量,例如 int8、uint8、int16、uint16 等,无论平台如何,它们都是正确的大小(而 c++11 自己使用标头 stdint. H)

最近在我正在编写的一个小型库中使用二进制文件 i/o 之后,我可以看到以这种方式使用 typedef 以确保代码可移植的好处。

但是,如果我要麻烦键入“namespace::uint32”而不是使用内置的基本类型,我不妨尽可能使替换有用。因此,我正在考虑使用类而不是简单的 typedef。

这些包装类将实现所有普通运算符,因此可以与基本类型互换使用。

例如:

int x = 0;
//do stuff

可能成为

class intWrapper {
//whatever
};

intWrapper = 0;
//do stuff

无需修改“//do stuff”中的任何代码

我之所以考虑这种方法而不仅仅是 typedefs 是因为我已经拥有对基本类型进行操作的函数,例如

std::string numberToString(double toConvert);

std::string numberToHexString(double toConvert);

int intToXSignificantPlaces(const int& number, 
                               unsigned char numberOfSignificantPlaces);

bool numbersAreApproximatelyEqual(float tollerance);
//etc....

从语法上讲,执行以下操作会更好(并且更多 oop):

intWrapper.toString();
intWrapper.toHexString();
//etc

此外,它还允许我实现 bigint 类(int128 等)并让那些和较小的类(基于基本类型)使用相同的接口。

最后,每个包装器都可以有一个自己的静态实例,称为 max 和 min,因此 int32::max 和 int32::min 的良好语法是可能的。

但是,在执行此操作之前,我有一些问题需要解决(因为它主要是语法糖,并且这些类型将被使用,因此通常任何额外的开销都会对性能产生重大影响)。

1) 仅在 int a + int b 上使用 someClass.operator+()、someClass.operator-() 等时是否有任何额外的函数调用开销?如果是这样,内联 operator+() 会消除所有这些开销吗?

2)所有外部函数都需要原始类型,例如glVertex3f(float,float,float)不能简单地传递3个floatWrapper对象,有没有办法让编译器自动将floatWrapper转换为float?如果是这样,是否有性能影响?

3)是否有任何额外的内存开销?我理解(?)具有继承的类具有某种虚拟表指针,因此使用稍微多一点的内存(或者只是用于虚拟函数?),但假设这些包装类不是从/不是子类继承的t 任何额外的内存使用使用类而不是基本类型,有吗?

4) 这可能导致任何其他问题/性能影响吗?

4

4 回答 4

14

1) 使用 someClass.operator+() 时是否有任何额外的函数调用开销

不,如果函数体很小并且在头中,它将被内联,并且没有开销

2) 有没有办法让编译器自动将 floatWrapper 转换为浮点数?

struct floatWrapper {
    floatWrapper(float); //implicit conversion from float
    operator float(); //implicit conversion to float.  
};

同样,如果函数的主体很小并且在标头中,它将被内联,并且没有开销

3)是否有任何额外的内存开销?

如果没有虚拟功能,则不会。如果一个类声明或继承了任何虚函数,则该类称为多态类。如果类不是多态的,则对象不需要包含指向虚函数表的指针。此外,不允许将指向非多态类的指针/引用沿继承层次结构向下执行到派生类的指针/引用的动态转换,因此对象不需要具有某种类型信息。

4) 这可能导致任何其他问题/性能影响吗?

表现?不。

此外,请务必将不修改 lhs 的二元运算符实现为自由函数,并重载它们以支持floatWrapperand的所有相关排列float

struct floatWrapper {
    explicit floatWrapper(float);
    operator float(); //implicit conversion to float.  
    floatWrapper operator-=(float);
};
floatWrapper operator-(floatWrapper lhs, floatWrapper rhs) 
{return lhs-=rhs;}
floatWrapper operator-(float lhs, floatWrapper rhs) 
{return floatWrapper(lhs)-=rhs;}
floatWrapper operator-(floatWrapper lhs, float rhs) 
{return lhs-=rhs;}

这是我对这种事情的尝试。请注意,对于 float/double/long double,您需要稍微不同的版本。

于 2013-07-22T17:09:47.347 回答
3

这取决于编译器。如果它有循环或分配,则不太可能被内联。

于 2013-07-22T17:23:42.853 回答
0

我认为答案并不完全正确——至少对于 gcc 4,由于构造函数和操作符调用,我观察到了显着的开销。

下面的时间大约是 with 的两倍long

typedef intWrapperImpl<long> TestWrapper;
//typedef long TestWrapper;

int main (int argc, char** argv) {
    TestWrapper sum(0);
    TestWrapper test(4);

    for (long i = 0; i < 1000000000L; ++i) {
        sum += test;
    }

    cout << sum << "\n";

    return 0;
}

使用不同版本的 gcc 4 优化和不优化导致性能没有差异。

在这种情况下,添加

intWrapperImpl& operator+=(const intWrapperImpl & v) {value+=v.value; return *this;}

只带来轻微的改善。

使用这样的包装器,就好像它们是性能关键代码中的基本类型一样,似乎是个坏主意。在本地使用它们并因此一直调用构造函数似乎更糟。

这真的让我感到惊讶,因为它应该很容易内联所有内容并对其进行优化,就好像它是一个基本类型的变量一样。

任何进一步的提示将不胜感激!

于 2017-03-31T12:00:56.333 回答
0

以下简单的包装器可能会起作用:

class intWrapper {
private:
  int val;
public:
  intWrapper(int val = 0) : val(val) {}
  operator int &() { return val; }
  int* operator &() { return &val; }
};

为了使上面的代码更通用:

template <class T>
class PrimitiveWrapper {
private:
  T val;
public:
  PrimitiveWrapper(T val = 0) : val(val) {}
  operator T &() { return val; }
  T* operator &() { return &val; }
};

T原始类型在哪里。示例用法:

int main() {
  PrimitiveWrapper<int> a = 1;
  a += 1;
  std::cout << a << std::endl;
}

我为上面的代码尝试了 gcc,我看到开销被完全优化了。这是合理的,因为包装器非常简单。但是,这可能因编译器而异,当逻辑变得更复杂时,优化可能会变得更弱。

于 2021-10-07T00:50:07.883 回答