Pytanie Struktury C ++ z funkcjami członkowskimi a klasami ze zmiennymi publicznymi


To jest naprawdę kwestia dobrej formy / najlepszych praktyk. Używam structs w C ++ do tworzenia obiektów, które są zaprojektowane tak, aby zasadniczo przechowywać dane, zamiast tworzyć klasę z tonami metod dostępu, które nic nie robią, ale pobierają / ustawiają wartości. Na przykład:

struct Person {
    std::string name;
    DateObject dob;
    (...)
};

Jeśli wyobrażasz sobie 20 dodatkowych zmiennych, pisanie tego jako klasy z prywatnymi członkami i 40-osobowymi akcesorami jest bólem do zarządzania i wydaje się dla mnie marnotrawstwem.

Czasami jednak może potrzebuję dodać trochę minimalnej funkcjonalności do danych. W tym przykładzie mówię, że czasami potrzebuję też wieku, na podstawie danych:

struct Person {
    std::string name;
    DateObject dob;
    (...)
    int age() {return calculated age from dob;}
}

Oczywiście dla każdej złożonej funkcjonalności stworzyłem klasę, ale dla prostej tylko takiej funkcjonalności, jest to "zły projekt"? Jeśli używam klasy, czy jest to zła forma utrzymywania zmiennych danych jako publicznych elementów klasy, czy też po prostu muszę ją zaakceptować i tworzyć klasy przy użyciu kilku metod dostępu? Rozumiem różnice między klasami i strukturami, po prostu pytam o najlepsze praktyki.


21
2018-03-22 14:21


pochodzenie


Pisanie metod dostępu (getter / setter) jest dobrym pomysłem. Jeśli po prostu trochę google, prawdopodobnie znajdziesz mnóstwo dyskusji na ten temat. Wskazówka: Większość IDE zapewnia funkcje do automatycznego generowania programów pobierających i ustawiających. - Sebastian Hoffmann
class i struct typy różnią się tym, że domyślna kontrola dostępu a class jest privatei domyślna kontrola dostępu dla a struct jest publiczne. Użycie słów kluczowych poza tworzeniem typu może się nieznacznie różnić ... ale wydajesz się inwestować dużo energii w różnicę między słowem struct i class w typie. Może być używany do dokumentacji w konkretnym projekcie, ale jest to specyficzne dla projektu. - Yakk - Adam Nevraumont
Istnieje również opcja bezpłatnej funkcji int age(const Person& person). Nie wszystko musi być Obiektem z Metody. - molbdnilo
Możliwy duplikat stackoverflow.com/questions/3380633/...? - Polar


Odpowiedzi:


Myślę, że należy wziąć pod uwagę dwie ważne zasady projektowania:

  1. Ukryj reprezentację klasy przez interfejs, jeśli istnieje pewna niezmienność dla tej klasy.

    Klasa ma niezmiennik, gdy istnieje coś takiego jak niepoprawny stan dla tej klasy. Klasa powinna zawsze zachować niezmienność.

    Rozważmy Point typ reprezentujący dwuwymiarowy punkt geometryczny. To powinno być po prostu struct z publicznym x i y członkowie danych. Nie ma czegoś takiego jak nieważny punkt. Każda kombinacja x i y wartości są idealnie w porządku.

    W przypadku a Person, czy ma niezmienniki, zależy całkowicie od problemu. Czy uważasz takie rzeczy za puste imię jako prawidłowe imię? Czy można Person mieć dowolną datę urodzenia? W twoim przypadku myślę, że odpowiedź brzmi "tak", a twoja klasa powinna informować członków o tym fakcie.

    Widzieć: Klasy powinny egzekwować niezmienniki

  2. Nieprzyjemne funkcje nie będące członkami poprawiają hermetyzację.

    Nie ma powodu dla ciebie age funkcja powinna być zaimplementowana jako funkcja składowa. Wynik age można obliczyć przy użyciu publicznego interfejsu Person, więc nie ma powodu, aby pełnić funkcję członka. Umieść go w tej samej przestrzeni nazw co Person tak, aby został znaleziony przez wyszukiwanie zależne od argumentów. Funkcje znalezione przez ADL są częścią interfejsu tej klasy; po prostu nie mają dostępu do prywatnych danych.

    Jeśli zrobiłeś to funkcją członkowską i pewnego dnia wprowadziłeś jakiś prywatny stan Personmiałbyś niepotrzebną zależność. Nagle age ma większy dostęp do danych, niż potrzebuje.

    Widzieć: Jak funkcje poza Członkiem poprawiają enkapsulację

Oto, jak go zrealizować:

struct Person {
  std::string name;
  DateObject dob;
};

int age(const Person& person) {
  return calculated age from person.dob;
}

19
2018-03-22 14:43



Doskonała odpowiedź i dokładnie taki rodzaj informacji, którego szukałem, dzięki! - amnesia
Zgadzam się z tą odpowiedzią. Dodałbym również to określenie agejako funkcja członkowska jest kiepskim sprzężeniem kontrolnym - osoby dzwoniące musiałyby przekazać aktualną datę do metody, aby mieć dobre wyczucie tego, co robi, lub bieżąca data musiałaby zostać pobrana z innego miejsca, co nie byłoby oczywiste dla dzwoniący. age udostępnia więcej implementacji typu interfejsowi klienta niż jest to konieczne. Mogłoby zostać zachowane jako funkcja członkowska, gdyby zagwarantować, że struktura ta nigdy nie stanie się bardziej skomplikowana, ale nawet wtedy zadeklarowałbym to inline i dodaj currentDate parametr. - Michael Hoffmann
@Joseph Mansfield "Umieść go w tej samej przestrzeni nazw co Person", nie spodziewalibyśmy się zobaczyć namespace PeopleStuff {...} enkapsulując swój blok kodu? Czy to wystarczy age() być w tym samym pliku? - nobism


W C ++, Structs są klasami, z tylko Różnica (przynajmniej w moim przekonaniu) jest taka, że ​​członkowie Structs są domyślnie publiczni, ale w klasach są prywatni. Oznacza to, że używanie Struktur jest całkowicie dopuszczalne - Ten artykuł wyjaśnia to dobrze.


6
2018-03-22 14:24





W C ++ jedyną różnicą między strukturami i klasami jest to, że struktury są domyślnie widoczne publicznie. Dobrą wskazówką jest stosowanie struktur jako zwykłych starych danych (POD), które przechowują tylko dane i klasy używane, gdy wymagana jest większa funkcjonalność (funkcje składowe).

Nadal możesz zastanawiać się, czy po prostu mieć publiczne zmienne w klasie lub korzystać z funkcji członka; rozważ następujący scenariusz.

Powiedzmy, że masz klasę A który ma funkcję GetSomeVariable to jest tylko getter dla zmiennej prywatnej:

class A
{
    double _someVariable;

public:
    double GetSomeVariable() { return _someVariable; }
};

Co, jeśli za dwadzieścia lat zmieni się znaczenie tej zmiennej i musisz, powiedzmy, pomnożyć ją przez 0,5? Podczas używania gettera jest to proste; po prostu zwróć zmienną pomnożoną przez 0,5:

    double GetSomeVariable() { return 0.5*_someVariable; }

W ten sposób pozwalasz na łatwą konserwację i pozwalasz na łatwą modyfikację.


1
2018-03-22 14:29



To dobry pomysł unikaj nazywania identyfikatora z wiodącym podkreśleniem. Dla zmiennych członków klasy rozważ someVariable_ lub m_someVariable zamiast. - DavidRR


Jeśli chcesz mieć jakiegoś posiadacza danych, wolisz strukturę bez żadnych metod pobierania / ustawiania.

Jeśli jest coś więcej, jak w tym przypadku "Osoba".

  1. Modeluje rzeczywisty byt świata,
  2. Ma określony stan i zachowanie,
  3. Współdziała ze światem zewnętrznym,
  4. Wykazuje proste / złożone relacje z innymi podmiotami,
  5. może ewoluować w nadgodzinach,

to jest idealny kandydat na klasę.


1
2018-03-22 14:33





"Użyj struct tylko dla obiektów pasywnych, które przenoszą dane, wszystko inne jest klasą."

mówić Google Guidlines, Robię to w ten sposób i znajduję dobrą regułę. Poza tym myślę, że możesz zdefiniować własną pragmatykę lub odejść od tej reguły, jeśli ma to naprawdę sens.


0
2018-03-22 14:26





Nie chcę tu błyszczeć świętej wojny; Zazwyczaj go rozróżniam w ten sposób:

  • W przypadku obiektów POD (tj. Tylko danych, bez narażonych zachowań) deklaruj internalizery i uzyskuj do nich bezpośredni dostęp. Użycie struct słowo kluczowe jest tutaj wygodne i służy jako wskazówka do wykorzystania obiektu.
  • W przypadku obiektów nieposiadających POD zadeklaruj obiekty wewnętrzne jako prywatne i określ publiczne zestawy pobierające / ustawiające. Użycie class słowo kluczowe jest bardziej naturalne w tych przypadkach.

0
2018-03-22 14:31