1 Востаннє редагувалося leofun01 (23.06.2016 22:11:52)

Тема: Як зробити код переносимим/сумісним для різних компіляторів ?

Є в мене код на C++ (загалом), успішно компілюється, добре працює, але тільки в мене (ну і на інших машинах, якщо поставити той самий компілятор).
І є проблемка, цей код в майбутньому буде компілюватися на машинах, про які мені нічого не відомо. Не відомо які операційні системи, які компілятори там будуть поставлені. Відомо лише, що доставляти новіші компілятори ніхто не буде і мені треба подбати про те, щоб код компілювався всюди. Як мені це зробити ?
З самого початку код писався на Visual C++ 2013. Пробував запускати старішими компіляторами, і там були деякі несумісні куски коду, ну я прочитав про макроси визначені різними компіляторами, і зумів зробити код сумісним з усіма лайнософтівськими компіляторами починаючи з 2005 року випуску.
В двох словах / сильно спрощені приклади коду:
Було

void func(const int &i) { /* ... */ }
void func(int &&i) { /* ... */ }
void main()
{
    int &&i00 = 0;
    int i01 = i00;
    int &i02 = i01;
    func(std::move(i00));
    func(i01);
    func(i02);
}

Стало

#if !defined(nullptr) && (!defined(_MSC_VER) || _MSC_VER < 1600)
#define nullptr 0
#endif // #if !defined(nullptr) && (!defined(_MSC_VER) || _MSC_VER < 1600)

void func(const int &i) { /* ... */ }
#if !defined(_MSC_VER) || _MSC_VER >= 1600
void func(int &&i) { /* ... */ }
#endif // end for #if
void main()
{
    #if !defined(_MSC_VER) || _MSC_VER >= 1600
    int &&i00 = 0;
    int i01 = i00;
    #else
    int i01 = 0;
    #endif // #if !defined(_MSC_VER) || _MSC_VER >= 1600 #else
    int &i02 = i01;
    #if !defined(_MSC_VER) || _MSC_VER >= 1600
    func(std::move(i00));
    #endif // #if !defined(_MSC_VER) || _MSC_VER >= 1600
    func(i01);
    func(i02);
}

Наведений код, звичайно, не той, над яким я працюю, але цей приклад дуже вдало передає суть. Розмір коду виріс, майже в 2 рази, і це тільки для Visual C++. А ще потрібно для інших постаратись.
Це я виклав, щоб показати, які способи я використовую на шляху до сумісності. Чи є ще якісь способи ?

Є досить велика ймовірність, що код буде компілюватись під лінукси і маки. Із цього виникає питання: Як дізнатись, які можливості підтримують різні компілятори різних версій ?
Може є якийсь повний список в тирнеті ?, де це все можна почитати...

Крім того в деяких старих компіляторах можна зустріти баги типу такого.
Чи є якісь списки, де можна зустріти всі можливі баги ?

І що Ви робите, коли потрібно досягнути сумісності з різними компіляторами ?
Якщо знаєте якісь туторіали по цьому, накидайте посилань сюди.

Подякували: 0xDADA11C71

2 Востаннє редагувалося 0x9111A (23.06.2016 21:52:00)

Re: Як зробити код переносимим/сумісним для різних компіляторів ?

Список називається стандарт, будь то C++ 98/03/11/14. Вибираєтете стандарт і пишете з використанням стандартних фіч. Уникаєте "компайлер-специфік" фіч, або надійно іфдефами обгортайте.
Думаю простіше буде написати такий код відразу використовуючи gcc/clang з перевіркою в msvc.

+ раз уже взялись за кросплатформеність то посадіть то все на відповідну білд-систему, наприклад cmake

Подякували: leofun01, koala2

3

Re: Як зробити код переносимим/сумісним для різних компіляторів ?

Якщо відверто не розумію фічі з нулптр, записав = 0 і все, нафік взагалі nullptr

Подякували: leofun011

4 Востаннє редагувалося -=ЮрА=- (23.06.2016 22:33:03)

Re: Як зробити код переносимим/сумісним для різних компіляторів ?

leofun01 тобі потрібні макроси _WIN32 _MAC  та _LINUX(якщо під різні платформи пишеш), якщо ж тільки під він, то пиши здебільшого в С-стилі (його зжує компллер від будь-якої IDE) тоді версії MSC_VER взагалі будуть не потрібні. Відверто не зрозумів поданої проблеми. Взагалі любий компілер у стандарті зжує код у станарті, всі ці MSC_VER роблять код неперносимим а тому поганим.

Подякували: leofun011

5 Востаннє редагувалося leofun01 (23.06.2016 23:16:16)

Re: Як зробити код переносимим/сумісним для різних компіляторів ?

0x9111A написав:

Список називається стандарт, будь то C++ 98/03/11/14. Вибираєтете стандарт і пишете з використанням стандартних фіч.

В цьому й проблема, я не можу вибрати якийсь один стандарт. В основному орієнтуюсь на C++11 і в деяких дуже рідкісних випадках на C++14 (в цілях максимальної оптимізації), але якщо попаде компілятор старіший на стільки, що не підтримуватиме ті всі нововведення, то потрібно забезпечити коректну роботу іншими кусками коду.
Я сподівався, що є універсальні макроси, якими можна визначити, який стандарт підтримується даним компілятором, але схоже, що в кожного компілятора вони свої (різні).


-=ЮрА=- написав:

Якщо відверто не розумію фічі з нулптр, записав = 0 і все, нафік взагалі nullptr

Поправді, я теж не розумію, але VisualC++ (починаючи з 2010) трохи свариться (warning) на 0/NULL, коли я його присвоюю якомусь вказівнику, пише що треба використовувати nullptr.
Починаючи з 2010 версії nullptr використовується в VisualC++ як порожній вказівник (вказівник на ніщо). Це при тому, що до 2008-ої (включно) він не підтримувався, хоч і був зарезервованим ключовим словом.
Короче Microsoft качає права.

-=ЮрА=- написав:

всі ці MSC_VER роблять код неперносимим а тому поганим.

Наоборот, я використовую всі ці _MSC_VER для того, щоб забезпечити переносимість. _MSC_VER в коді застосований так, щоб не мішати іншим компіляторам.

6

Re: Як зробити код переносимим/сумісним для різних компіляторів ?

Якщо вже вирішили ось таке писати, то раджу зробити кілька макросів для визначення наявності певних особливостей, на кшталт

#if (!defined(_MSC_VER) || _MSC_VER < 1600)
#define MYLIB_MOVE_SEMANTICS
#define MYLIB_NULLPTR
....
#endif//if (!defined(_MSC_VER) || _MSC_VER < 1600)

а потім для суперечливих конструкцій проголошувати власні макроси, а не використовувати стандартні імена:

#ifndef MYLIB_NULLPTR
#define NULLPTR 0
#else //#ifndef MYLIB_NULLPTR
#define NULLPTR nullptr
#endif//#ifndef MYLIB_NULLPTR
...
if( NULLPTR == ptr ) {
  /*і пишемо тепер в такому стилі, а не сподіваємося, що в C++17 
  nullptr буде працювати абсолютно так само, як і 0 в старих*/

Що ж до того, які компілятори з якої версії що підтримують:
https://gcc.gnu.org/projects/cxx-status.html
http://clang.llvm.org/cxx_status.html
https://msdn.microsoft.com/uk-ua/en-en/ … 67368.aspx

Подякували: Torbins, leofun012

7 Востаннє редагувалося leofun01 (27.06.2016 22:59:46)

Re: Як зробити код переносимим/сумісним для різних компіляторів ?

Велика дяка koala і 0x9111A. Зробив собі файл-заготовку:

// MacrosDefines.h

#if !defined(_CPP_98) && ( \
    (defined(__cplusplus) && __cplusplus >= 199711L) || \
    (defined(_MSC_VER) && _MSC_VER >= 1400))
        #define _CPP_98
#endif

#if !defined(_CPP_03) && \
    (defined(_MSC_VER) && _MSC_VER >= 1500)
        #define _CPP_03
#endif

#if !defined(_MOVE_SEMANTIC) && ( \
    (defined(__cplusplus) && __cplusplus >= 201103L) || \
    (defined(_MSC_VER) && _MSC_VER >= 1600))
        #define _MOVE_SEMANTIC
#endif

#if !defined(_CPP_11) && ( \
    (defined(__cplusplus) && __cplusplus >= 201103L) || \
    (defined(_MSC_VER) && _MSC_VER >= 1700))
        #define _CPP_11
#endif

#if !defined(_CPP_14) && ( \
    (defined(__cplusplus) && __cplusplus >= 201402L) || \
    (defined(_MSC_VER) && _MSC_VER >= 1900))
        #define _CPP_14
#endif

#ifdef _MSC_VER
    // for visual studio only.
    //#pragma warning(disable : 4355)
    #pragma warning(disable : 4706)
#endif

/* Define _NULL_PTR pointer value */
#ifndef _NULL_PTR
    #if defined(nullptr) || defined(_CPP_11) || \
        (defined(_MSC_VER) && _MSC_VER >= 1600)
            #define _NULL_PTR nullptr
    #elif defined(NULL)
        #define _NULL_PTR NULL
    //#elif defined(_CPP_98)
    #else
        #ifdef __cplusplus
            #define _NULL_PTR 0
        #else  // __cplusplus
            #define _NULL_PTR ((void *)0)
        #endif // __cplusplus
    #endif
#endif // _NULL_PTR

#include "MacrosConfig.h"
#if !defined(_USE_MUTEX) && defined(_TRY_MUTEX) && \
    defined(_MSC_VER) && _MSC_VER >= 1800
        #define _USE_MUTEX
#endif


/*//
useful info:

value categories:
http://en.cppreference.com/w/cpp/language/value_category
http://en.cppreference.com/w/cpp/preprocessor/replace
http://stackoverflow.com/questions/3601602/what-are-rvalues-lvalues-xvalues-glvalues-and-prvalues

compiler macros:
http://www.cplusplus.com/doc/tutorial/preprocessor/
http://en.cppreference.com/w/cpp/compiler_support
http://clang.llvm.org/cxx_status.html
http://clang.llvm.org/hacking.html
http://gcc.gnu.org/projects/cxx-status.html
http://llvm.org/docs/CodingStandards.html
http://sourceforge.net/p/predef/wiki/Compilers/
http://stackoverflow.com/questions/2324658/how-to-determine-the-version-of-the-c-standard-used-by-the-compiler
//*/
MacrosConfig.h
// MacrosConfig.h

// Use _TRY_MUTEX if you want to use Mutex
#ifndef _TRY_MUTEX
// Uncomment line 7 if you want to use std::mutex by default.
// Comment line 7 if you want to use CRITICAL_SECTION by default.
#define _TRY_MUTEX
#endif

#ifndef _TRY_DEF_FUNCTION
// Uncomment line 13 if you want to declare functions using #define by default.
// Comment line 13 if you want to declare functions using inline C++ syntax by default.
#define _TRY_DEF_FUNCTION
#endif

Підключаю файл MacrosDefines.h всюди, де потрібно відірватись від платформи, використовую #ifdef _CPP_?? (або _MOVE_SEMANTIC), і наче працює.
Це ні в якому разі не приклад того як правильно робити, бо nullptr i 0 - не одне й те саме, але новачкам буде корисно.

Подякували: koala1

8

Re: Як зробити код переносимим/сумісним для різних компіляторів ?

А навіщо ви ворнінг відключаєте?

Подякували: leofun011

9 Востаннє редагувалося leofun01 (29.06.2016 00:04:30)

Re: Як зробити код переносимим/сумісним для різних компіляторів ?

Трохи відредагував, бо там помилки були в коді.

0x9111A написав:

А навіщо ви ворнінг відкючаєте?

warning C4355: 'this' : used in base member initializer list.
В мене там було кілька місць, де this використовувався як параметр для базового конструктора, тому раніше був відключив. Але після того, як я привів код до стану, в якому він компілюється всюди, потреба у відключенні warning'а відпала, я видалив ту стрічку в себе і більше не передаю this конструкторам предків.

warning C4706: assignment within conditional expression.
В мене часто в "if" і "while" зустрічається присвоення, я не збираюсь їх позбуватись перетворюючи 1 рядок на окремі 3. І дивитись при компіляції на 200 варнінгів теж не хочу, тому відключив.
Думаю цей варнінг був введений, щоб новачки не плутали if(a = b) з if(a == b).

10 Востаннє редагувалося koala (29.06.2016 09:13:29)

Re: Як зробити код переносимим/сумісним для різних компіляторів ?

В VS радять не просто вимикати варнінги, а робити так:

//початок коду
#pragma warning( push )
#pragma warning( disable : 4355 )
#pragma warning( disable : 4706 )
...
//кінець коду
#pragma warning( pop ) 

Тоді інші користувачі вашого коду будуть отримувати всі свої варнінги, а не дивуватися, чому VS не попередив.

Крім того, дуже раджу замість

if( a = b )

писати

if( ( a = b ) != 0 )

чи навіть (Йода-стайл)

if( 0 != ( a = b ) )

Тоді і попередження не буде, і код прямо вказує, де присвоювання, а де порівняння.

Подякували: 0x9111A, leofun012

11

Re: Як зробити код переносимим/сумісним для різних компіляторів ?

Ще раз переконався, що нічого не знаю.

12 Востаннє редагувалося XXX) (20.07.2016 21:35:03)

Re: Як зробити код переносимим/сумісним для різних компіляторів ?

leofun01 написав:
-=ЮрА=- написав:

Якщо відверто не розумію фічі з нулптр, записав = 0 і все, нафік взагалі nullptr

Поправді, я теж не розумію, але VisualC++ (починаючи з 2010) трохи свариться (warning) на 0/NULL, коли я його присвоюю якомусь вказівнику, пише що треба використовувати nullptr.

Компілятор nullptr i NULL переводить в 0, але NULL це задефайнений інт 0, а nullptr є вказівником(тобто типобезпечнішим). Наведу приклад:
void f(char const *ptr);
void f(int v);

f(NULL); // який метод викличеться? (приклад взято з Стековерфлов)

Подякували: leofun011

13

Re: Як зробити код переносимим/сумісним для різних компіляторів ?

В MacrosDefines.h добавив ще пару кусків:

#ifndef _OVERRIDE_
    #if (defined(__cplusplus) && __cplusplus >= 201103L) || \
        (defined(_MSC_VER) && _MSC_VER >= 1600) // MSVC2010
        #define _OVERRIDE_ override
    #else
        #define _OVERRIDE_ 
    #endif
#endif

#ifndef _FINAL_
    #if (defined(__cplusplus) && __cplusplus >= 201103L) || \
        (defined(_MSC_VER) && _MSC_VER >= 1700) // MSVC2012
        #define _FINAL_ final
    #elif (defined(_MSC_VER) && _MSC_VER >= 1600) // MSVC2010
        #define _FINAL_ sealed
    #else
        #define _FINAL_ 
    #endif
#endif

І тепер всюди, де в C++11 використовується override, пишу _OVERRIDE_.
А замість final / sealed (в перевизначених запечатаних методах) пишу _FINAL_.

Подякували: Betterthanyou1

14

Re: Як зробити код переносимим/сумісним для різних компіляторів ?

В мене знову питання:
Мені потрібно зробити макрос, який я зможу викликати замість std::move.
Як краще зробити define-функцію (макрос) для std::move ?

Мені потрібна функція (макрос), щоб не писати всюди це:

value =
    #ifdef _MOVE_SEMANTIC
        std::move<T &>
    #endif
    (lvalue);

Спочатку хотів зробити найпростіше

#if !defined(_MOVE_SEMANTIC) && ( \
    (defined(__cplusplus) && __cplusplus >= 201103L) || \
    (defined(_MSC_VER) && _MSC_VER >= 1600)) // MSVC2010
        #define _MOVE_SEMANTIC
        #define _STD_MOVE std::move
#else
        #define _STD_MOVE
#endif

але помітив, що такий варіант може мати побічні ефекти (std::move є шаблонною функцією і в такому варіанті вона буде сама визначати тип аргумента). А мені потрібно, щоб розробники могли явно вказувати тип аргумента (навіть якщо доведеться його всюди писати явно).
Крім того, якщо використовувати цей варіант, то це буде виглядати так:

_STD_MOVE<T &>(value);

І компілятори, які не підтримують переміщення, будуть повертати помилки компіляції, бо вираз буде перетворюватися в

<T &>(value);

Тож тепер розглядаю наступні 2 варіанти:

#if !defined(_MOVE_SEMANTIC) && ( \
    (defined(__cplusplus) && __cplusplus >= 201103L) || \
    (defined(_MSC_VER) && _MSC_VER >= 1600)) // MSVC2010
        #define _MOVE_SEMANTIC
        #define _STD_MOVE(T) std::move<T>
#else
        #define _STD_MOVE(T)
#endif

тоді можна буде викликати так:

_STD_MOVE(T &)(value);

або

#if !defined(_MOVE_SEMANTIC) && ( \
    (defined(__cplusplus) && __cplusplus >= 201103L) || \
    (defined(_MSC_VER) && _MSC_VER >= 1600)) // MSVC2010
        #define _MOVE_SEMANTIC
        #define _STD_MOVE(T, value) std::move<T>(value)
#else
        #define _STD_MOVE(T)
#endif

тоді можна буде викликати так:

_STD_MOVE(T &,value);

Який варіант кращий ? або може я йду зовсім не в тому напрямку ?

15

Re: Як зробити код переносимим/сумісним для різних компіляторів ?

Ви, гадаю, не тим займаєтеся. Можливо написати шар сумісності, який буде робити щось у різних формах залежно від компілятора, але тільки за умови, що всі компілятора це вміють. Семантика переміщення можлива тільки якщо її підтримує компілятор; якщо ви її ігноруватимете, програма працюватиме не так, як очікував програміст. Вам треба або повністю реалізовувати переміщення в старих компіляторах, або не чіпати його.

Подякували: leofun011

16

Re: Як зробити код переносимим/сумісним для різних компіляторів ?

koala написав:

Ви, гадаю, не тим займаєтеся.

Цілком можливо. Але маю проблему і не знаю як її вирішити.
Проблема в тому, що в коді дуже часто (більше 20) разів зустрічається кусок коду типу:

value =
    #ifdef _MOVE_SEMANTIC
        std::move<T &>
    #endif
    (lvalue);

і дивлячись на своє завдання, бачу, що цей код буде зустрічатись ще багато разів у майбутньому.
Дуже хочеться замінити його на щось маленьке (хоча б однострічкове).

koala написав:

Можливо написати шар сумісності, який буде робити щось у різних формах залежно від компілятора, але тільки за умови, що всі компілятора це вміють. Семантика переміщення можлива тільки якщо її підтримує компілятор; якщо ви її ігноруватимете, програма працюватиме не так, як очікував програміст.

В мене головна мета зробити проект таким, щоб його можна було успішно скомпілювати навіть на дуже старих компіляторах.
Я пишу код (свою частину проекту) так, щоб він працював однаково (як з переміщенням, так і без нього), тобто про проблеми виконання програми сильно замислюватись не доводиться. Переміщення в мене всюди підстраховано копіюванням, тобто якщо компілятор не підтримує переміщення, то не страшно (запуститься копіювання, яке дасть такий самий кінцевий результат при виконанні моєї програми), але якщо переміщення підтримується, то програма буде працювати швидше.

koala написав:

Вам треба або повністю реалізовувати переміщення в старих компіляторах, або не чіпати його.

Ну воно в мене наче реалізовано, не знаю наскільки повністю.
Я не намагаюсь застосовувати запропоновані макроси до класів сторонніх розробників, ці макроси використовуються тільки всередині методів, які належать моїм класам. Думаю в інших розробників не виникне проблем з використанням моїх класів.