É uma má idéia ter um método de classe que é passado variáveis ​​de classe?


14

Aqui está o que eu quero dizer:

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);
}

addjá pode acessar todas as variáveis ​​de que precisa, já que é um método de classe, então é uma má idéia? Existem razões pelas quais devo ou não fazer isso?


13
Se você quiser fazer isso, isso indica um design ruim. As propriedades da classe provavelmente pertencem a outro lugar.
bitsoflogic

11
O snippet é muito pequeno. Esse nível de abstrações mistas não ocorre em trechos desse tamanho. Você também pode corrigir isso com um bom texto sobre considerações de design.
Joshua

16
Sinceramente, não entendo por que você faria isso. O que você ganha? Por que não apenas ter um addmétodo sem argumento que opera diretamente em seus internos? Só por que?
Ian Kemp

2
@IanKemp Ou tem um addmétodo que aceita argumentos, mas não existe como parte de uma classe. Apenas uma função pura para adicionar duas matrizes.
Kevin

O método add sempre adiciona arr1 e arr2, ou pode ser usado para adicionar algo a alguma coisa?
user253751

Respostas:


33

Pode haver coisas com a classe que eu faria diferente, mas para responder à pergunta direta, minha resposta seria

sim, é uma má ideia

Minha principal razão para isso é que você não tem controle sobre o que é passado para a addfunção. Claro que você espera que seja uma das matrizes de membros, mas o que acontece se alguém passar em uma matriz diferente que tenha um tamanho menor que 100 ou se você tiver um comprimento maior que 100?

O que acontece é que você criou a possibilidade de saturação de buffer. E isso é uma coisa ruim por toda parte.

Para responder a algumas perguntas mais óbvias (para mim):

  1. Você está misturando matrizes de estilo C com C ++. Não sou um guru de C ++, mas sei que o C ++ tem maneiras melhores (mais seguras) de lidar com matrizes

  2. Se a classe já possui as variáveis ​​de membro, por que você precisa passá-las? Esta é mais uma questão arquitetônica.

Outras pessoas com mais experiência em C ++ (eu parei de usá-lo há 10 ou 15 anos) podem ter maneiras mais eloquentes de explicar os problemas e provavelmente também apresentarão mais problemas.


De fato, vector<int>seria mais adequado; comprimento seria então inútil. Como alternativa const int len, como a matriz de comprimento variável não faz parte do padrão C ++ (embora alguns compiladores a suportem, porque é um recurso opcional em C).
Christophe

5
O @Christophe Author estava usando uma matriz de tamanho fixo, que deve ser substituída por uma std::arrayque não decaia ao passar para parâmetros, tem uma maneira mais conveniente de obter seu tamanho, é copiável e tem acesso à verificação de limites opcional, sem sobrecarga Matrizes de estilo C.
Cubic

1
@ Cubic: sempre que vejo código declarando matrizes com um tamanho fixo arbitrário (como 100), tenho certeza de que o autor é um iniciante que não tem idéia das implicações dessa bobagem, e é uma boa ideia recomendá-lo alterando esse design para algo com um tamanho variável.
Doc Brown

As matrizes estão bem.
Lightness Races com Monica

... para aqueles que não entenderam o que quero dizer, veja também Zero One Infinity Rule
Doc Brown

48

Chamar um método de classe com algumas variáveis ​​de classe não é necessariamente ruim. Mas fazer isso de fora da classe é uma péssima idéia e sugere uma falha fundamental no seu projeto de OO, ou seja, a ausência de encapsulamento adequado :

  • Qualquer código que use sua classe precisará saber qual lené o comprimento da matriz e usá-lo de forma consistente. Isso vai contra o princípio do mínimo conhecimento . Essa dependência dos detalhes internos da classe é extremamente suscetível a erros e arriscada.
  • Isso tornaria a evolução da classe muito difícil (por exemplo, se um dia, você gostaria de alterar as matrizes herdadas e lenpara uma mais moderna std::vector<int>), pois exigiria que você também alterasse todo o código usando sua classe.
  • Qualquer parte do código pode causar estragos em seus MyClassobjetos, corrompendo algumas variáveis ​​públicas sem respeitar as regras (os chamados invariantes de classe )
  • Finalmente, o método é, na realidade, independente da classe, uma vez que trabalha apenas com os parâmetros e não depende de nenhum outro elemento da classe. Esse tipo de método poderia muito bem ser uma função independente fora da classe. Ou pelo menos um método estático.

Você deve refatorar seu código e:

  • faça suas variáveis ​​de classe privateou protected, a menos que haja uma boa razão para não fazê-lo.
  • crie seus métodos públicos como ações a serem executadas na classe (por exemplo object.action(additional parameters for the action):).
  • Se após essa refatoração, você ainda tiver alguns métodos de classe que precisam ser chamados com variáveis ​​de classe, torná-los protegidos ou privados após verificar que são funções de utilitário que suportam os métodos públicos.

7

Quando a intenção deste design é que você deseja reutilizar esse método para dados que não provêm dessa instância de classe, você pode declará-lo como um staticmétodo .

Um método estático não pertence a nenhuma instância particular de uma classe. É um método da própria classe. Como os métodos estáticos não são executados no contexto de nenhuma instância específica da classe, eles só podem acessar variáveis ​​de membro que também são declaradas como estáticas. Considerando que seu método addnão se refere a nenhuma das variáveis-membro declaradas MyClass, é um bom candidato para ser declarado como estático.

No entanto, os problemas de segurança mencionados pelas outras respostas são válidos: Você não está verificando se as duas matrizes são pelo menos lenlongas. Se você deseja escrever C ++ moderno e robusto, evite usar matrizes. Use a classe std::vectorsempre que possível. Ao contrário de matrizes regulares, os vetores conhecem seu próprio tamanho. Portanto, você não precisa acompanhar o tamanho deles. A maioria (e não todos!) De seus métodos também faz verificação automática de limites, o que garante que você receba uma exceção ao ler ou escrever além do final. O acesso regular à matriz não faz a verificação vinculada, o que resulta em uma falha padrão na melhor das hipóteses e pode causar uma vulnerabilidade de estouro de buffer explorável na pior das hipóteses .


1

Um método em uma classe manipula idealmente dados encapsulados dentro da classe. No seu exemplo, não há razão para o método add ter parâmetros, basta usar as propriedades da classe.


0

Passar (parte de) um objeto para uma função membro é bom. Ao implementar suas funções, você sempre deve estar ciente de um possível alias e das conseqüências disso.

Obviamente, o contrato pode deixar alguns tipos de alias indefinidos, mesmo que o idioma o suporte.

Exemplos proeminentes:

  • Auto-atribuição. Deve ser um, possivelmente caro, não operacional. Não pessimize o caso comum para isso.
    A atribuição de movimentação automática, por outro lado, embora não deva explodir na sua cara, não precisa ser um não-op.
  • Inserir uma cópia de um elemento em um contêiner no contêiner. Algo como v.push_back(v[2]);.
  • std::copy() assume que o destino não começa dentro da fonte.

Mas seu exemplo tem problemas diferentes:

  • A função de membro não usa nenhum de seus privilégios, nem se refere a this. Ele age como uma função de utilidade gratuita, que está simplesmente no lugar errado.
  • Sua turma não tenta apresentar nenhum tipo de abstração . De acordo com isso, ele não tenta encapsular suas entranhas, nem manter invariantes . É um saco idiota de elementos arbitrários.

Em resumo: Sim, este exemplo é ruim. Mas não porque a função membro é passada para objetos membro.


0

Fazer isso especificamente é uma má ideia por vários bons motivos, mas não tenho certeza de que todos sejam parte integrante da sua pergunta:

  • Ter variáveis ​​de classe (variáveis ​​reais, não constantes) é algo a ser evitado, se possível, e deve desencadear uma reflexão séria sobre a necessidade absoluta.
  • Deixar o acesso de gravação a essas variáveis ​​de classe completamente aberto a todos é quase universalmente ruim.
  • Seu método de classe acaba não sendo relacionado à sua classe. O que realmente é é uma função que adiciona os valores de uma matriz aos valores de outra matriz. Esse tipo de função deve ser uma função em uma linguagem que possui funções e deve ser um método estático de uma "classe falsa" (ou seja, uma coleção de funções sem dados ou comportamento de objeto) em linguagens que não possuem funções.
  • Como alternativa, remova esses parâmetros e transforme-os em um método de classe real, mas ainda assim seria ruim pelos motivos mencionados anteriormente.
  • Por que matrizes int? vector<int>tornaria sua vida mais fácil.
  • Se este exemplo é realmente representativo do seu design, considere colocar seus dados em objetos e não em classes e também implementar construtores para esses objetos de uma maneira que não exija a alteração do valor dos dados do objeto após a criação.

0

Esta é uma abordagem clássica "C com classes" para C ++. Na realidade, não é isso que qualquer programador C ++ experiente escreveria. Por um lado, o uso de matrizes C brutas é praticamente universalmente desencorajado, a menos que você esteja implementando uma biblioteca de contêineres.

Algo assim seria mais apropriado:

// 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
    }
}
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.