使类方法传递类变量是一个坏主意吗?


14

这就是我的意思:

class MyClass {
    int arr1[100];
    int arr2[100];
    int len = 100;

    void add(int* x1, int* x2, int size) {
        for (int i = 0; i < size; i++) {
            x1[i] += x2[i];
        }
    }
};

int main() {
    MyClass myInstance;

    // Fill the arrays...

    myInstance.add(myInstance.arr1, myInstance.arr2, myInstance.len);
}

add因为它是一个类方法,已经可以访问它需要的所有变量了,所以这不是一个好主意吗?有什么理由我应该或不应该这样做?


13
如果您想这样做,则表明设计不好。该类的属性可能属于其他地方。
–bitsoflogic

11
片段太小。这种大小的摘要中不会出现这种混合抽象级别。您也可以在设计注意事项上写一些好的文字来解决此问题。
约书亚

16
老实说,我不明白你为什么还要这么做。你得到什么?为什么不让no-arg add方法直接在其内部运行呢?只是,为什么?
伊恩·肯普

2
@IanKemp或者有一个带有add参数但不存在于类中的方法。只是将两个数组加在一起的一个纯函数。
凯文(Kevin)

add方法是始终添加arr1和arr2,还是可以将任何内容添加到任何内容?
user253751 '18

Answers:


33

在课堂上,我可能会有一些不同的事情,但是要回答直接的问题,我的答案是

是的,这是一个坏主意

我这样做的主要原因是您无法控制传递给 add函数的内容。当然,您希望它是成员数组之一,但是如果有人通过的另一个数组的大小小于100,或者您通过的长度大于100,会发生什么呢?

发生的情况是您造成了缓冲区溢出的可能性。到处都是坏事。

要(对我)回答一些明显的问题:

  1. 您正在将C样式数组与C ++混合在一起。我不是C ++专家,但我确实知道C ++具有更好(更安全)的数组处理方式

  2. 如果该类已经具有成员变量,那么为什么需要传递它们?这更多是建筑问题。

其他具有更多C ++经验的人(我在10或15年前就停止使用它了)可能有更雄辩的方法来解释问题,并且可能还会提出更多的问题。


确实,vector<int>会更合适;那么长度将是无用的。另外const int len,由于可变长度数组不是C ++标准的一部分(尽管某些编译器支持它,因为它是C中的可选功能)。
Christophe

5
@Christophe Author使用的是固定大小的数组,应将其替换为固定大小的数组,将std::array其传递给参数时不会衰减,它具有更方便的获取大小的方法,可复制并可以通过可选的边界检查进行访问,同时没有开销C样式数组。
立方

1
@Cubic:每当我看到代码以任意固定大小(例如100)声明数组时,我都可以肯定作者是一个初学者,不了解这种废话的含义,建议他/她是一个好主意将此设计更改为尺寸可变的东西。
布朗


...对于那些不理解我的意思的人,另请参见零一无穷规则
布朗

48

用一些类变量调用一个类方法不一定是不好的。但是,从课堂之外进行此操作是一个非常糟糕的主意,它暗示了OO设计中的一个基本缺陷,即缺少适当的封装

  • 使用您的类的任何代码都需要知道len数组的长度,并始终使用它。这违背了知识最少原则。对班级内部细节的这种依赖非常容易出错和冒险。
  • 这将使类的演化变得非常困难(例如,如果有一天,您想要更改旧数组并len改为更现代的数组std::vector<int>),因为这将要求您也使用类更改所有代码。
  • 代码的任何部分都可能MyClass通过破坏某些公共变量而不遵守规则来破坏对象(所谓的类不变式
  • 最后,该方法实际上与类无关,因为它仅与参数一起使用,并且不依赖于其他类元素。这种方法很可能是类之外的独立函数。或至少是静态方法。

您应该重构代码,并:

  • 使您的类变量privateprotected,除非有充分的理由不这样做。
  • 将您的公共方法设计为要在类上执行的操作(例如: object.action(additional parameters for the action)
  • 如果在此重构之后,您仍有一些需要使用类变量调用的类方法,请在确认它们是支持公共方法的实用函数后,将其设置为保护或私有。

7

如果本设计的目的是希望能够将此方法重用于非此类实例的数据,则可能需要将其声明为staticmethod

静态方法不属于类的任何实例。而是类本身的一种方法。因为静态方法不在类的任何特定实例的上下文中运行,所以它们只能访问也声明为静态的成员变量。考虑到您的方法add未引用在中声明的任何成员变量MyClass,因此它是将其声明为静态的一个很好的候选者。

但是,其他答案提到的安全问题是有效的:您没有检查两个数组是否至少都len长。如果要编写现代而强大的C ++,则应避免使用数组。std::vector尽可能使用该类。与常规数组相反,向量知道它们自己的大小。因此,您无需自己跟踪它们的大小。他们的大多数(不是全部!)方法也进行自动边界检查,这保证了在读或写结束时会得到异常。常规的数组访问不执行边界检查,这最多会导致段错误,更糟糕的是,这可能会导致可利用的缓冲区溢出漏洞


1

理想情况下,类中的方法可在类内部操作封装的数据。在您的示例中,没有理由让add方法具有任何参数,只需使用类的属性即可。


0

将对象的(一部分)传递给成员函数很好。在实现功能时,您始终必须意识到可能出现的别名及其后果。

当然,即使语言支持,合同也可能会保留一些未定义的别名。

突出的例子:

  • 自我分配。这应该是一个可能非常昂贵的禁止操作。不要为这种情况而悲观。
    另一方面,自我分配不应该在你的脸上爆炸,但不必空手。
  • 将容器中元素的副本插入容器中。有点像v.push_back(v[2]);
  • std::copy() 假定目标不是在源内部开始。

但是您的示例存在不同的问题:

  • 成员函数不使用其任何特权,也不引用this。它的作用就像一个免费的实用程序功能,只是放在错误的位置。
  • 您的课堂不会尝试提出任何抽象。与此相一致,它不尝试封装其内部,也不维护任何不变式。这是一个笨拙的任意元素袋。

总结:是的,这个例子很糟糕。但这不是因为成员函数传递了成员对象。


0

具体地讲,这样做是一个坏主意,原因有几个,但我不确定它们是否对您的问题必不可少:

  • 尽可能避免使用类变量(实际变量,而不是常量),并且应该引起对是否绝对需要它的认真思考。
  • 将写这些类变量的访问权限完全向所有人开放几乎是普遍不利的。
  • 您的类方法最终与您的类无关。它实际上是一个将一个数组的值添加到另一个数组的值的函数。这种功能应该是具有功能的语言中的功能,并且应该是不具有功能的语言中“伪类”的静态方法(即,没有数据或对象行为的功能的集合)。
  • 或者,删除这些参数并将其转换为真实的类方法,但是由于前面提到的原因,它仍然很糟糕。
  • 为什么要使用int数组?vector<int>会让您的生活更轻松。
  • 如果此示例确实代表您的设计,请考虑将数据放入对象而不是类中,并以不需要在创建后更改对象数据值的方式实现这些对象的构造函数。

0

这是C ++的经典“带有类的C”方法。实际上,这不是任何经验丰富的C ++程序员都会写的。首先,不鼓励使用原始C数组,除非您要实现容器库。

这样的事情会更合适:

// Don't forget to compile with -std=c++17
#include <iostream>
using std::cout; // This style is less common, but I prefer it
using std::endl; // I'm sure it'll incite some strongly opinionated comments

#include <array>
using std::array;

#include <algorithm>

#include <vector>
using std::vector;


class MyClass {
public: // Begin lazy for brevity; don't do this
    std::array<int, 5> arr1 = {1, 2, 3, 4, 5};
    std::array<int, 5> arr2 = {10, 10, 10, 10, 10};
};

void elementwise_add_assign(
    std::array<int, 5>& lhs,
    const std::array<int, 5>& rhs
) {
    std::transform(
        lhs.begin(), lhs.end(), rhs.begin(),
        lhs.begin(),
        [](auto& l, const auto& r) -> int {
            return l + r;
        });
}

int main() {
    MyClass foo{};

    elementwise_add_assign(foo.arr1, foo.arr2);

    for(auto const& value: foo.arr1) {
        cout << value << endl; // arr1 values have been added to
    }

    for(auto const& value: foo.arr2) {
        cout << value << endl; // arr2 values remain unaltered
    }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.