Czy używając kodu Win32 w swojej nowoczesnej aplikacji C++, powinieneś używać odpowiedniego rzutowania?

Na przykład w dokumentacji MSDN można znaleźć następujące obsady:

(LPTSTR)&lpMsgBuf

Czy powinienem zawracać sobie głowę konwersją tego na:

static_cast<LPTSTR>(&lpMsgBuf);

A może powinienem po prostu zostawić wszystkie idiomatyczne części Win32 w stylu C, tak jak zwykle znajdują się w dokumentach, i zapisać bardziej idiomatyczny styl/wykorzystanie C++ dla reszty mojego kodu?


person ApplePieIsGood    schedule 27.04.2009    source źródło


Odpowiedzi (6)


Rzuty w nowym stylu zostały wprowadzone nie bez powodu: są bezpieczniejsze, bardziej objaśniające/komentujące same siebie, łatwiejsze do zobaczenia i łatwiejsze do wyszukania.

Więc korzystaj z nich.

Mówiąc bardziej wyjaśniająco, mamy na myśli, że nie możesz po prostu rzutować na coś, musisz powiedzieć dlaczego rzutowania (rzutuję w hierarchii dziedziczenia (dynamic_cast), moje rzutowanie jest zdefiniowane w implementacji i prawdopodobnie nie jest przenośny (reinterpret_cast), odrzucam stałość (const_cast) itp.).

Są celowo długie i brzydkie, aby obsada rzucała się na czytelnika (i aby zniechęcić do stylu programowania, który wykorzystuje zbyt dużo castingu).

Są bezpieczniejsze, ponieważ np. nie można odrzucić stałości bez wyraźnego zrobienia tego.

person tpdi    schedule 27.04.2009

Zamiast zasypywać mój kod rzutowaniami w starym lub nowym stylu, skorzystałbym z przeciążenia operatora C++, aby dodać przeciążone wbudowane wersje funkcji API systemu Windows, które przyjmują parametry odpowiednich typów. (Dopóki jest to udokumentowane dla nowych programistów, mam nadzieję, że nie będzie to zbyt mylące.)

Na przykład piątym parametrem FormatMessage jest zwykle LPTSTR, ale jeśli przekażesz flagę FORMAT_MESSAGE_ALLOCATE_BUFFER, piąty parametr będzie wskaźnikiem do LPTSTR. Zdefiniowałbym więc taką funkcję:

inline DWORD WINAPI FormatMessage(
  __in      DWORD dwFlags,
  __in_opt  LPCVOID lpSource,
  __in      DWORD dwMessageId,
  __in      DWORD dwLanguageId,
  __out     LPTSTR *lpBuffer,
  __in      DWORD nSize,
  __in_opt  va_list *Arguments
) {
    assert(dwFlags & FORMAT_MESSAGE_ALLOCATE_BUFFER);
    return FormatMessage(dwFlags, lpSource, dwMessageId, dwLanguageId, static_cast<LPTSTR>(&lpBuffer), nSize, Arguments);
}
person Josh Kelley    schedule 27.04.2009

Dokumentacja MSDN a standard C++.

Wybrałbym to później. Myślę, że jest to jedna z zasad omawianych również w skutecznym C++. NIE powinieneś mieszać dwóch stylów w jednym programie.

person J.W.    schedule 27.04.2009

Nie powinieneś w ogóle potrzebować rzutowania - wygląda na to, że lpMsgBuf jest wskaźnikiem do tablicy znaków (z kontekstu nie jest jasne, czy są to znaki ANSI, czy szerokie znaki), które możesz przekazać bezpośrednio do różnych Win32 Funkcje.

Nie ma potrzeby pobierania adresu — jeśli lpMsgBuf jest tablicą statyczną (np. char lpMsgBuf[SIZE]), to pobranie jej adresu jest zbędne i jest równoważne adresowi jej pierwszego elementu. Jeśli jest to wskaźnik, wówczas wzięcie jego adresu daje char** (lub wchar_t**, jeśli jest szeroki), czyli NIE jest tym, co chcesz przekazać - jeśli funkcja Win32 oczekuje LPSTR (tj. char*) i przekazujesz jej char** rzucony na char*, wyniknie z tego wiele zła. Kompilator nie pozwoli ci przekazać char** do funkcji oczekującej char* bez rzutowania - użycie rzutowania w celu wyciszenia kompilatora jest NIEPRAWIDŁOWE, ponieważ kompilator próbuje powiedzieć ci coś ważnego.

Jeśli w tym przypadku nie możesz przekazać żądanego parametru bez rzutowania, prawie na pewno przekazujesz go źle. Napraw kod, aby nie potrzebować rzutowania.

person Adam Rosenfield    schedule 27.04.2009
comment
Zarówno ty, jak i ja, przybyliśmy za późno. Kiedy pisaliśmy, zaakceptowano już odpowiedź, mówiącą, że ten najprawdopodobniej pełen błędów kod powinien najprawdopodobniej pozostać pełen błędów. Cóż za strata naszych wysiłków. - person Windows programmer; 27.04.2009
comment
Niestety, interfejs API systemu Windows czasami naprawdę wymaga pozornie złych rzutów, takich jak ten. FormatMessage jest przykładem. - person Josh Kelley; 27.04.2009
comment
@Programista Windows: To prawda, wzdycha @Josh Kelley: To prawda, szczególnie w odniesieniu do niektórych komunikatów Windows. WPARAM i LPARAM mogą być wszystkim pod słońcem. Ale bez kontekstu konkretnej funkcji, którą OP próbuje wywołać, zakładam najgorsze. - person Adam Rosenfield; 27.04.2009
comment
Tak, właśnie podałem to jako przykład, pytanie dotyczy bardziej stylu, a nie konkretnego przykładu, więc myślę, że poprawnie zaznaczyłem poprawną odpowiedź, ale przegłosowałem tę opcję, ponieważ jest to również przydatne. Dzięki. - person ApplePieIsGood; 27.04.2009

Jeśli lpMsgBuf jest już wskaźnikiem na ciąg T i jeśli interfejs API oczekuje, że wskaźnik będzie wskazywał bezpośrednio na ciąg T, nie powinieneś w ogóle używać rzutowania. Innymi słowy, jeśli typ danych jest już poprawny, dobrze jest nie używać niepotrzebnych rzutowań. Powodem jest to, że nadużywanie rzutowań może oślepić cię na błędy w tym samym czasie, gdy rzutowania zamykają kompilator.

Jeśli lpMsgBuf jest już wskaźnikiem do ciągu T i jeśli interfejs API oczekuje wskaźnika do ciągu T, ale oczekuje, że wskaźnik do wskaźnika zostanie rzutowany na wskaźnik typu na ciąg T (a interfejs API go rzuci z powrotem podczas korzystania z niego), to robisz to dobrze.

Jeśli lpMsgBuf jest wskaźnikiem do czegoś innego (poprawnego typu) i API oczekuje wskaźnika do Twojego typu, ale API oczekuje tych gier castingowych, to robisz to dobrze.

Jeśli lpMsgBuf jest wskaźnikiem do ciągu T i jeśli API oczekuje wskaźnika do ciągu T, to za pomocą operatora & konstruujesz wskaźnik do wskaźnika do ciągu T, którego API nie oczekuje, i używając rzutowania, mówisz kompilatorowi, aby zamienił ten wskaźnik na wskaźnik we wskaźnik typu na ciąg T, mimo że wartość nadal wskazuje na wskaźnik. Więc pomyślnie zamknąłeś kompilator, ale nadal dostarczasz śmieciowe wyniki sobie lub swoim klientom.

Tak więc bez dodatkowych szczegółów na temat tego, która strona MSDN każe Ci utworzyć wskaźnik do wskaźnika i grać w gry castingowe dla jakiego API, nie możemy zgadnąć, czy robisz to dobrze, czy błędnie odczytasz MSDN, czy też MSDN mówi napisać zły kod.

Teraz, jeśli rzutowanie jest rzeczywiście prawidłowe, wybór składni, której chcesz użyć, zależy od stylu, jaki zastosujesz w pozostałej części programu.

person Windows programmer    schedule 27.04.2009

W większości przykładowy kod MSDN Win32 jest kompatybilny z C i C++. Używanie zwykłego, starego C do pisania programów Windows jest całkowicie dopuszczalne (i było w pewnym momencie standardową praktyką).

Ponieważ rzuty C++ nie są dostępne w C, modyfikowanie przykładów w celu wykorzystania ich wymagałoby utrzymywania dwóch kopii przykładowego kodu. Ponieważ kod C działa tak samo jak w C++, łatwiej jest po prostu pozostawić rzutowanie w starym stylu.

person Eclipse    schedule 27.04.2009