Sınıf değişkenleri geçirilen bir sınıf yöntemi yapmak kötü bir fikir mi?


14

Demek istediğim şu:

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

addbir sınıf yöntemi olduğundan, tüm değişkenlere zaten erişebilir, bu kötü bir fikir midir? Bunu yapmam ya da yapmamam için nedenler var mı?


13
Bunu yapmak istiyorsanız, kötü bir tasarıma işaret ediyor. Sınıfın özellikleri muhtemelen başka bir yere aittir.
bitsoflogic

11
Snippet çok küçük. Bu karışık soyutlamalar, bu boyuttaki parçacıklarda meydana gelmez. Bunu tasarımla ilgili bazı iyi metinlerle de düzeltebilirsiniz.
Joshua

16
Dürüst olmak gerekirse, bunu neden yapabileceğini bile anlamıyorum. Ne kazanıyorsunuz? Neden sadece addiçsel olarak doğrudan çalışan arg-olmayan bir yöntemimiz olmasın ? Sadece neden?
Ian Kemp

2
@IanKemp Veya addbağımsız değişkenleri alan ancak bir sınıfın parçası olarak bulunmayan bir yönteminiz olabilir. İki diziyi bir araya getirmek için saf bir işlev.
Kevin

Add yöntemi her zaman arr1 ve arr2 ekler mi, yoksa herhangi bir şeye herhangi bir şey eklemek için kullanılabilir mi?
user253751

Yanıtlar:


33

Sınıfta farklı yapacağım şeyler olabilir, ama doğrudan soruyu cevaplamak için cevabım

evet, bu kötü bir fikir

Bunun ana sebebi, addişleve neyin aktarıldığı üzerinde hiçbir kontrolünüz olmamasıdır . Üye dizilerinden biri olduğunu umarsınız, ancak biri 100'den küçük boyutlu farklı bir diziden geçerse veya 100'den büyük bir uzunlukta geçerseniz ne olur?

Olan şey, bir arabellek taşması olasılığını yaratmış olmanızdır. Ve bu her yerde kötü bir şey.

Açıkça sorulan bazı soruları yanıtlamak için

  1. C stili dizileri C ++ ile karıştırıyorsunuz. Ben C ++ guru değilim, ama C ++ dizileri işlemek için daha iyi (daha güvenli) yollara sahip olduğunu biliyorum

  2. Sınıfta zaten üye değişkenler varsa, neden bunları iletmeniz gerekiyor? Bu daha mimari bir sorudur.

Daha fazla C ++ deneyimine sahip diğer insanlar (10 veya 15 yıl önce kullanmayı bıraktım) sorunları açıklamanın daha etkili yolları olabilir ve muhtemelen daha fazla sorun ortaya çıkaracaktır.


Gerçekten vector<int>daha uygun olurdu; bu durumda uzunluk işe yaramaz. Alternatif olarak const int len, değişken uzunluk dizisi C ++ standardının bir parçası olmadığından (bazı derleyiciler bunu desteklese de, C'de isteğe bağlı bir özelliktir).
Christophe

5
@Christophe Yazar, std::arrayparametrelere iletilirken bozulmayan, boyutunu almanın daha kolay bir yoluna sahip, kopyalanabilir ve isteğe bağlı sınır kontrolü ile erişime sahipken değiştirilmesi gereken sabit boyutlu bir dizi kullanıyordu . C tarzı diziler.
Kübik

1
@Cubic: Kodları rastgele sabit boyutlu (100 gibi) gösteren kodları her gördüğümde, yazarın bu saçmalığın etkileri hakkında hiçbir fikri olmayan bir acemi olduğundan eminim ve onu tavsiye etmek iyi bir fikirdir. bu tasarımı değişken boyutlu bir şeye dönüştürmek.
Doc Brown


... ne demek istediğimi anlamayanlar için bkz. Zero One Infinity Rule
Doc Brown

48

Bazı sınıf değişkenleriyle bir sınıf yöntemini çağırmak her zaman kötü değildir. Ancak bunu sınıfın dışından yapmak çok kötü bir fikirdir ve OO tasarımınızda temel bir kusur olduğunu, yani uygun kapsüllemenin bulunmadığını gösterir :

  • Sınıfınızı kullanan herhangi bir kodun len, dizinin uzunluğu olduğunu bilmesi ve sürekli olarak kullanması gerekir. Bu , en az bilgi ilkesine aykırıdır . Sınıfın iç detaylarına böyle bağımlılık son derece hataya açık ve risklidir.
  • Bu, sınıfın evrimini çok zorlaştıracaktır (örneğin, bir gün eski dizileri değiştirmek ve lendaha modern bir hale std::vector<int>getirmek isterseniz), çünkü sınıfınızı kullanarak tüm kodu değiştirmenizi gerektirecektir.
  • Kodun herhangi bir kısmı, kurallara uymadan MyClassbazı ortak değişkenleri bozarak nesnelerinize zarar verebilir ( sınıf değişmezleri olarak adlandırılır )
  • Son olarak, yöntem gerçekte sınıftan bağımsızdır, çünkü sadece parametrelerle çalışır ve başka bir sınıf öğesine bağlı değildir. Bu tür bir yöntem sınıf dışında bağımsız bir işlev olabilir. Veya en azından statik bir yöntem.

Kodunuzu yeniden düzenlemelisiniz ve:

  • Sınıf değişkenlerinizi yapın privateveya protectedyapmamak için iyi bir neden olmadığı sürece.
  • herkese açık yöntemlerinizi sınıfta gerçekleştirilecek eylemler olarak tasarlayın (örneğin:) object.action(additional parameters for the action).
  • Bu yeniden düzenleme işleminden sonra, sınıf değişkenleriyle çağrılması gereken bazı sınıf yöntemleriniz varsa, genel yöntemleri destekleyen yardımcı program işlevi olduklarını doğruladıktan sonra bunları korumalı veya özel yapın.

7

Bu tasarımın amacı, bu yöntemi bu sınıf örneğinden gelmeyen veriler için yeniden kullanmak istediğiniz olduğunda, bunu bir staticyöntem olarak bildirmek isteyebilirsiniz .

Statik bir yöntem, bir sınıfın herhangi bir parçalı örneğine ait değildir. Daha ziyade sınıfın kendisinin bir yöntemidir. Statik yöntemler, sınıfın belirli bir örneği bağlamında çalışmadığından, yalnızca statik olarak bildirilen üye değişkenlere erişebilirler. Metodunuzun addbeyan edilen üye değişkenlerden hiçbirini ifade etmediği düşünüldüğünde, MyClassstatik olarak bildirilmek için iyi bir adaydır.

Ancak, diğer yanıtlarda belirtilen güvenlik sorunları geçerlidir: Her iki dizinin de en azından lenuzun olup olmadığını denetlemezsiniz . Modern ve sağlam C ++ yazmak istiyorsanız, dizileri kullanmaktan kaçınmalısınız. std::vectorMümkünse sınıfı kullanın. Normal dizilerin aksine, vektörler kendi boyutlarını bilir. Yani boyutlarını kendiniz takip etmenize gerek yok. Metodlarının çoğu (hepsi değil!) Ayrıca, sonu okuduğunuzda veya yazdığınızda bir istisna almanızı garanti eden otomatik bağlı kontrol de yapar. Düzenli dizi erişimi, en iyi şekilde bir segfault ile sonuçlanan ve daha kötü bir şekilde bir arabellek taşması güvenlik açığına neden olabilecek sınır denetimi yapmaz .


1

Sınıftaki bir yöntem, sınıf içindeki kapsüllenmiş verileri ideal olarak manipüle eder. Örneğinizde, add yönteminin herhangi bir parametreye sahip olması için bir neden yoktur, sadece sınıfın özelliklerini kullanın.


0

Bir nesnenin üye işlevine (bir kısmının) iletilmesi iyidir. Fonksiyonlarınızı uygularken, her zaman gereken olası aliasing farkında olması ve bunların sonuçları.

Tabii ki, sözleşme, dil desteklese bile, bazı tür örtüşme tanımsız bırakabilir.

Öne çıkan örnekler:

  • Kendinden atama. Bu, muhtemelen pahalı bir işlem olmamalıdır. Bunun için ortak durumu kötüleştirmeyin.
    Öte yandan, kendi kendine hareket etme ödevi, yüzünüzde patlamaması gerekirken, hayır-op olması gerekmez.
  • Kaptaki bir öğenin bir kopyasını kaba ekleme. Gibi bir şey v.push_back(v[2]);.
  • std::copy() hedefin kaynağın içinde başlamadığını varsayar.

Ancak örneğinizin farklı sorunları var:

  • Üye işlevi kendi ayrıcalıklarından hiçbirini kullanmaz ya da söz konusu değildir this. Sadece yanlış yerde bulunan ücretsiz bir yardımcı program işlevi görür.
  • Sınıfınız herhangi bir soyutlama sunmaya çalışmaz . Buna paralel olarak, kendi doğasını kapsüllemeye veya herhangi bir değişmezi korumaya çalışmaz . Bu keyfi öğelerin aptal bir torbası.

Özetle: Evet, bu örnek kötü. Ancak üye işlevi, üye nesnelerini geçtiği için değil.


0

Özellikle bunu yapmak birkaç iyi nedenden dolayı kötü bir fikirdir, ancak hepsinin sorunuzun ayrılmaz olduğundan emin değilim:

  • Sınıf değişkenlerine (sabitler değil, gerçek değişkenler) sahip olmak, mümkünse kaçınılması gereken bir şeydir ve kesinlikle ihtiyacınız olup olmadığı konusunda ciddi bir yansımayı tetiklemelidir.
  • Bu sınıf değişkenlerine yazma erişimini tamamen herkese açık bırakmak neredeyse evrensel olarak kötüdür.
  • Sınıf yönteminiz, sınıfınızla ilgisiz hale gelir. Gerçekten bir dizinin değerlerini başka bir dizinin değerlerine ekleyen bir işlevdir. Bu tür bir işlev, işlevleri olan bir dilde bir işlev olmalı ve işlevleri olmayan dillerde "sahte sınıf" ın (yani veri veya nesne davranışı olmayan işlevlerin bir toplamı) statik bir yöntemi olmalıdır.
  • Alternatif olarak, bu parametreleri kaldırın ve gerçek bir sınıf yöntemine dönüştürün, ancak daha önce belirtilen nedenlerden dolayı hala kötü olacaktır.
  • Neden int dizileri? vector<int>hayatınızı kolaylaştıracaktır.
  • Bu örnek tasarımınızı gerçekten temsil ediyorsa, verilerinizi sınıflara değil nesnelere koymayı ve bu nesneler için oluşturulduktan sonra nesne verilerinin değerini değiştirmeyi gerektirmeyecek şekilde yapıcılar uygulamayı düşünün.

0

Bu C ++ 'a klasik "sınıflı C" yaklaşımıdır. Gerçekte, tecrübeli bir C ++ programcısının yazdığı şey bu değildir. Birincisi, bir konteyner kütüphanesi uygulamadığınız sürece, ham C dizilerini kullanmak neredeyse evrensel olarak önerilmez.

Böyle bir şey daha uygun olurdu:

// 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
    }
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.