Funkcje składowe a funkcje niebędące członkami dla operatorów matematycznych

12

Piszę bibliotekę algebry liniowej (krótko mówiąc, jest to zadanie szkolne), która obejmuje macierze, wektory itp. W trakcie tworzenia tej biblioteki będę tworzyć funkcje, które wykonują operacje matematyczne na obiektach. Na przykład transponuj macierz, odwróć macierz, znormalizuj wektor itp.

Byłem ciekawy, jaka jest „najlepsza praktyka” dla tego rodzaju funkcji ... To znaczy, czy powinienem uczynić funkcję funkcją składową, czy też nie-członkiem? (Dla zachowania przejrzystości / wykorzystania biblioteki)

Przykład:

//Member function way:
B = A.transpose();
C = A.inverse();

//Non-member function way:
B = linalg::transpose(A); //Non-member transpose function in linear algebra namespace
C = linalg::inverse(A);

Czy istnieje jakiś standard dotyczący tego rodzaju operacji? A przynajmniej czy istnieje jakiś powszechny sposób, w jaki ludzie to robią? Skłaniam się ku pierwszej opcji, ale chciałbym wiedzieć, czy jest to zalecane.

apnorton
źródło

Odpowiedzi:

7

To tylko kwestia stylu i smaku. Widziałem też różne biblioteki algebry liniowej

  • napisane w stylu OOP przy użyciu funkcji składowych

  • napisane w stylu innym niż OOP przy użyciu tylko bezpłatnych funkcji

  • zapewniając interfejs API z obydwoma

i wszyscy działali. Przynajmniej interfejs API powinien być spójny, więc wybierz swój wybór i upewnij się, że nie mieszasz tych stylów arbitralnie.

Doktor Brown
źródło
8

Unikaj opłat członkowskich: tam, gdzie to możliwe, wolą robić funkcje nieprzyjazne członkom.

Nieosobowe funkcje nieprzyjazne poprawiają enkapsulację poprzez minimalizowanie zależności: Ciało funkcji nie może zależeć od niepublicznych członków klasy (patrz punkt 11). Rozbijają także klasy monolityczne, aby uwolnić rozdzielną funkcjonalność, co dodatkowo zmniejsza sprzężenie (patrz pozycja 33). Poprawiają ogólność, ponieważ trudno jest pisać szablony, które nie wiedzą, czy operacja jest członkiem dla danego typu [...]

Herb Sutter


źródło
1
Wreszcie dobra odpowiedź: późno, ale nie za późno. ;-) Kolejne odniesienie: GotW # 84: Monoliths „Unstrung”
Deduplicator
1

Zarówno funkcje członka, jak i nie będące członkami mają realne zalety oprócz zwykłego smaku. Na przykład, podstawowa tablica (tablice) zastosowana do implementacji matrixklasy prawdopodobnie będzie privatewięc musiała być używana, friendchyba że użyjesz funkcji członka. Pod tym względem projekt OO może być lepszy.

Należy jednak pamiętać, że od czasu do czasu informatycy muszą wdrażać złożoną formułę matematyczną, nie zawsze wiedząc, co naprawdę robią. Kiedy muszę to robić, wolę funkcje nie będące członkami, ponieważ wynik „wizualny” będzie bliższy oryginalnej formule matematycznej (ponieważ formuła matematyczna zazwyczaj używa stylu funkcjonalnego).

Ostatecznie odpowiedź jest taka sama jak u Doc Browna: to kwestia gustu. Krótko mówiąc, możesz rozważyć napisanie biblioteki w stylu OO z funkcjami składowymi (łatwiejsze pisanie, nie friend), a następnie napisanie funkcji niebędących członkami w celu wykonania tych samych zadań, które po prostu przekażą swoje argumenty do członków. Pozwala zachować spójność i pozwolić użytkownikowi wybrać to, co uważa za najlepsze.

Morwenn
źródło
0

Jeśli zanurzysz się głębiej w opisie problemu, jasne jest, że w pewnym momencie użyjesz niestandardowych struktur danych, które reprezentują określone byty matematyczne. Na przykład możesz reprezentować macierz jako środek n-wymiarowej tablicy. Aby rozwinąć swój przykład,

// C way
typedef struct {
    int data[MAX_LEN][MAX_LEN];
} MATRIX;

MATRIX B = transpose(A);

// C++ way
class Matrix; // Forward Declaration
class Matrix {
    int data[MAX_LEN][MAX_LEN];
    public:
        Matrix transpose();
};

Matrix B = A.transpose();

(Przykład w C ++ jest uproszczony, możesz użyć szablonów itp.)

Należy wziąć pod uwagę łatwość użycia niestandardowych struktur danych w obu przypadkach. C ++ (lub dowolny obiekt zorientowany) jako język jest sam w sobie potężniejszy i rozciąga się na konsumenta biblioteki w takim stopniu, w jakim przynosi korzyść implementatorowi. W przeszłości korzystałem tylko z bibliotek C, a te niestandardowe struktury danych zdają się być wszędzie dostępne w mgnieniu oka! Podczas gdy interoperacyjność jest łatwiejsza do osiągnięcia z tym drugim (może przy użyciu specjalnych konstruktorów?)

Na zakończenie, jeśli używasz C ++, zdecydowanie zalecane jest podejście obiektowe.

dotbugfix
źródło
5
Przepraszam, w twoim rozumowaniu jest bardzo mało solidnych faktów. Nie ma różnicy w łatwości użycia między twoimi dwoma transposeprzykładami. Głównym powodem, dla którego C ++ jest łatwiejszy, jest fakt, że semantyka kopiowania i przypisywania faktycznie działa. A = Bmoże skompilować w C, ale prawdopodobnie robi coś złego.
MSalters
+1 dla semantyki kopiowania Punkt, który próbowałem przejść, to używanie obiektów (enkapsulacji nad „reprezentacjami”) v / s przy użyciu luźno sprzężonego zestawu struktur i powiązanych funkcji.
dotbugfix