C++类的最佳实践

4

我希望了解一些在设计c++类时的最佳实践。

具体来说,我有一个名为Vec3的c++类。

class Vec3{
private:
    float elements[3];
public:
    Vec3(Vec3 v1){...}
    Vec3(int x, int y, int z){...}
    Vec3 add(Vec3 v1){...}
    Vec3 add(int x, int y, int z){...}
    ...
    Vec3 multiply(Vec3 v1){...}
    ...
    int dotProduct(Vec3 v1){...}
    Vec3 normalize(){...}
    ....
    int operator[](int pos){...}
};

所以,我有一个处理大小为3的向量计算的类。我想知道什么更好。使用指针还是不使用指针。
我的参数应该是指针并返回指针吗?还是不用指针?
Vec3 add(Vec3 v1) 或 Vec3* add(Vec3 v1) 或 Vec3* add(Vec3* v1) 或者......
现在我很困惑,我不知道是否应该在我的类中使用指针。我想可能总有一种方法可以将我的参数发送到不处理指针的函数......
Vec3* v2 = new Vec3(1,1,1);
Vec3 sum = v1.add(*v2);

我想提供一个可能是最好的解决方案,即同时具备这两个功能。

Vec3 add(Vec3 v2){...}
Vec3* add(Vec3* v2){...}

但我担心这会导致重复的代码,可能会有额外的开销。

谢谢你的回答……顺便说一句,我可以使用模板来更改向量的大小,但我更喜欢保持我的Vec3类单独,并创建一个Vec4类或将其命名为四元数。

编辑 这是我想出的解决方案。请随意评论、修改或重用代码。 还有一件事。我只想提到,在我的情况下,这个类应该是透明的。就像我们添加数字一样。

int i = 10;
int j = 15;
int k = i + k;

如果add重载修改调用函数的对象,在这种情况下是i,那么我最终得到的k将成为对i的引用,而i被赋值为25。但是我们真正想要的是k等于25,而i、k不变。
这就是我的类如何工作的。Vec3 k = i + k不会修改i或k,因为我们正在从这些值创建一个新数字。唯一需要返回引用的情况是+=、-=、++、--...,set([XYZ])?和normalize。
做一些像myvec.setX(10).normalize().scale(10)这样的事情可能很有趣。
注意:scale应该返回一个引用。我没有看到它,但我想这样可能更好。
Vec3 t = myvec.normalize().scale(100).copy();

http://pastebin.com/f413b7ffb

谢谢大家,我现在将开始处理Matrix类。
8个回答

8
这是我通常遵循的规则。请注意“通常”,有时候会因为某些原因而采取不同的做法...
对于我不打算修改的参数,如果它们不太大,我会传递值,因为它们将被复制。如果它们有点大或者不能被复制,你可以使用const引用或指针(我更喜欢const引用)。
对于我打算修改的参数,我使用引用。
对于返回值,我尽可能返回一个副本。有时候返回引用很方便(这对于单个函数的get/set非常有效,其中在获取或设置项目时不需要进行任何特殊处理)。
在我的观点中,指针真正发挥作用的地方是实例变量,我希望在何时构造或销毁它时拥有控制权。
希望这能有所帮助。

这正是我所需要的。谢谢你。我想我应该更多地利用参考资料。:) - Loïc Faure-Lacroix

3

向量具有已知的语义(对您和您的类的用户来说是已知的),因此我建议重载运算符(+,-,+=,-=)。在这样做时,我会使用常规定义而不是更改它们:

// instead of add:
class Vec3 {
public:
   Vec3& operator+=( Vec3 const & rhs );
};
// implemented as free function:
Vec3 operator+( Vec3 const &lhs, Vec3 const & rhs);

我建议避免使用指针。引用更加自然,只有在非常少数的情况下才需要使用指针而不是引用/值。避免复制你的函数(使用/不使用指针),因为这会使你的代码变得不必要地复杂,正如你在问题中已经发布的那样,你总是可以解除指针以检索引用。
提供一个常量和一个非常量operator[]:
class Vec3 {
public:
   float operator[]( size_t pos ) const; // returns copy, data does not change
   float& operator[]( size_t pos );  // returns a reference and allows changing the contents
};

编辑:我忘了提到size_t的细节:在索引参数中,最好使用无符号/size_t而不是有符号整数。


大多数情况下,这确实是正确的方式。顺便说一句,const下标运算符可以返回一个const引用(可能会产生一些有趣的副作用);有些人喜欢返回一个“const T”的值,而不仅仅是“T”,请参见该主题的"GOTW"。 - Luc Hermitte
大多数情况下:有时候用“”运算符实现“=”运算符更加容易,这对于矩阵来说也是如此。 - Luc Hermitte
这很有道理。有一件事我不太确定是否理解。如果我有一个const和非const函数,编译器会如何知道使用哪个?但现在,我将保留另一个函数,而不仅仅是运算符,因为我需要它们使得从C到C++的重构更容易。 - Loïc Faure-Lacroix
当存在一个const和一个非const的重载函数时,编译器总是会选择非const函数,除非在调用函数时对象是const的。请参见http://www.parashift.com/c++-faq-lite/const-correctness.html#faq-18.12。 - Luc Hermitte
@Luc Hermite:我不知道矩阵的具体情况,但通常是通过operator*=来定义operator+: X& operator+( X const & a, X const & ) { X tmp = a; tmp += b; return tmp; } - David Rodríguez - dribeas

3

由于int是基本类型,因此保持不变。对于任何使用vec3的内容,请使用引用。

例如:

Vec3 add(const Vec3 &v1){...}

在C语言中,你会使用指针来处理对象,但在C++中,通常使用引用更好。


1
如果你实现了像operator+=()operator*=()这样的运算符,你会希望它返回*this作为Vec3&
Vec3& operator+=(const Vec3& v2) {
    // add op
    return *this;
}

对于其他基本运算符,如operator+()和你的add(),你需要返回一个副本

Vec3 operator+(const Vec3& v2) {
    Vec3 ret;
    // add
    return ret;
}

1

在这种情况下,您几乎肯定不希望参数是指针。考虑以下示例:

// Error: not possible to take the address of the temporary
//        return value. 
v2.add(&someFunctionReturningVec3());

对于常量的引用没有问题。你甚至可以轻松地嵌套操作:

// declaration: Vec3 add(Vec3 const& v);
v2.add(v1.add(v3));

0
在这种情况下,没有必要将参数作为指针,而且你真的不应该像那样为所有运算符返回一个新对象。
在面向对象编程中,思想是对实际对象进行操作,例如
void add(Vec3 v1);
void multiply(Vec3 v1);

我还会说,你应该坚持使用Vec3对象作为参数(而不是x,y,z)。如果你只有x,y,z,可以调用add(Vec3(x,y,z))。


返回 *this 允许方法链接:v.add(Vec(1,2,3)).multiply(4);。当然,使用运算符重载会更好一些,除了优先级:(v += Vec(1,2,3)) *= 4;。op+ 和其它相关操作应该具有复制语义,但这会影响效率。 - Simon Buchan
add函数不应修改对象,因此我需要返回一个副本。就像写成j = i + k;其中i和k的值为10,最终得到i的值为20。这毫无意义。 - Loïc Faure-Lacroix
@Sybiam,我不同意。add()操作符确实应该修改对象。但是,如果你重载操作符,需要实现复制语义(正如Simon所指出的)。如果你想要在没有op+的情况下实现“j = i + k”,你应该写成Vec3 j(i); j.add(k);。 - E Dominique

0

1
如果不是在倡导自我赋值反模式,这将是一个很好的讨论。将二元运算符定义为成员函数也不是最先进的技术。 - Luc Hermitte

-2

正如greyfade所提到的,你应该关注复制语义。在这种情况下,你也应该添加这些方法:

class Vec3 {
public:
  Vec3(const Vec3& rhs) {
    copy(rhs);
  }

  Vec3 operator=(const Vec3& rhs) {
    copy(rhs);
    return *this;
  }

private:
  void copy(const Vec3& rhs) {
    // copy state from rhs
  }
};

op= 应该返回 Vec3&,而不是 Vec3。 - Simon Buchan
1
对于使用std容器或pod值的类,您不需要编写自己的复制函数 - 让编译器为您完成。 - Pete Kirkham

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接