Visual Studio 2015: нет предупреждения о несовпадении подписанных / неподписанных в std :: make_unique?

Я только что обнаружил ошибку в своем коде и очень озадачен тем, что это могло произойти, поскольку это простое несоответствие со знаком / без знака - чего вообще не должно происходить, потому что я компилирую с уровнем предупреждения 4, предупреждения как ошибки. Поэтому я попытался воспроизвести его, что довольно просто:

#include <memory>

class MyClass {
public:
    MyClass( unsigned ) {}
};
int main()
{
    MyClass* rawP = new MyClass(-1);                // issues a warning, as expected
    auto uniqueP = std::make_unique<MyClass>(-1);   // NO WARNING??!

    // silence the compiler
    rawP; 
    uniqueP;

    return 0;
}

Теперь я спрашиваю себя: в чем причина этого? Это ошибка VS или общий недостаток std :: make_unique? Есть ли способ исправить? (Обновление сообщества Visual Studio 2015 3)


person user2328447    schedule 21.02.2018    source источник


Ответы (1)


Вы видите комбинацию нескольких эффектов.

  1. Вызов в вашем main() совершенно законен, потому что экземпляр шаблона make_unique соответствует подписанному типу данных, который вы его передаете.
  2. Реализация make_unique не генерирует предупреждения, потому что предупреждения обычно отключены внутри системных заголовков.
  3. Visual Studio, похоже, не может обнаружить потенциальную (но не определенную) проблему преобразования знака внутри make_unique.

Подробнее:

1. Создание экземпляра шаблона действительно законно.

Типичная реализация std::make_unique выглядит так (сравните cppreference):

template <typename T, typename... Args>
inline std::unique_ptr<T> make_unique(Args&&... args)
{
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

В вашем случае при вызове std::make_unique<MyClass>(-1) шаблон создается для подписанного целого числа. Следовательно, вы не получаете предупреждения в своем коде, потому что преобразование без знака / знака не происходит.

2. Системные заголовки обычно отключают предупреждения.

Однако вы могли с полным правом ожидать предупреждения от make_unique реализации. В конце концов, когда new T(...) вызывается с вашим параметром со знаком, преобразование со знаком / без знака все равно происходит. В качестве примера возьмем следующую программу:

#include <memory>

class MyClass
{
public:
  MyClass(unsigned) { }
};

template <typename T, typename... Args>
inline std::unique_ptr<T> custom_make_unique(Args&&... args)
{
  return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}

int main()
{
  auto uniqueP = custom_make_unique<MyClass>(-1);
  (void) uniqueP;
  return 0;
}

Когда я компилирую это с помощью GCC с -Wsign-conversion, я получаю предупреждение

test.cpp: при создании экземпляра 'std :: unique_ptr ‹_Tp> custom_make_unique (Args && ...) [с T = MyClass; Args = {int}] ':
test.cpp: 17: 48: требуется отсюда
test.cpp: 12: 63: предупреждение: преобразование в' unsigned int 'из' int 'может изменить знак результат [-Wsign-conversion]
return std :: unique_ptr (new T (std :: forward (args) ...));

Возникает вопрос, почему вы не получаете это предупреждение для реализации std::make_unique()? По сути, ответ заключается в том, что компилятор заглушает эти предупреждения для своих системных заголовков. Например, версия заголовка <memory> GCC содержит прагму

#pragma GCC system_header

Как только эта прагма присутствует в файле заголовка, компилятор больше не сообщает предупреждения обо всем, что находится внутри этого заголовка. Из документации GCC:

Заголовочные файлы, объявляющие интерфейсы к операционной системе и библиотекам времени выполнения, часто не могут быть написаны на строго соответствующем языке C. Таким образом, GCC предоставляет коду, находящемуся в системных заголовках, особую обработку. Все предупреждения, кроме тех, которые генерируются «#warning» (см. «Диагностика»), подавляются, пока GCC обрабатывает системный заголовок.

См. Также эту публикацию SO для получения дополнительных сведений. Предположительно, аналогичный подход используется компилятором Visual Studio (и, как вы написали в своем комментарии, заголовок временно снижает уровень предупреждения).

3. Похоже, вы столкнулись с ограничением VisualStudio.

В случае с VisualStudio работает еще кое-что. Обратите внимание, как в предупреждении GCC выше говорится, что может быть проблема преобразования знака (в зависимости от того, какие значения пользователи позже будут вводить в custom_make_unique). Похоже, что VisualStudio может предупреждать только в случае определенной проблемы преобразования знака. См. Следующую программу:

#include <iostream>

void f(unsigned) { }

template <typename T>
void g(T val) { f(val); } // GCC issues a warning, VS does NOT

int main()
{
  f(-1); // GCC and VS issue a warning
  g(-1); // no conversion warning here (g<int> instantiated)
}

Попробуйте в Интернете.

person mindriot    schedule 21.02.2018
comment
Вау, это исчерпывающий ответ, большое спасибо. Я пробовал это с вашим custom_make_unique(), но он по-прежнему не выдает предупреждения (даже если также скопировать и вставить custom_forward и custom_remove_reference в мой собственный код). По крайней мере, теперь я знаю, что искать. - person user2328447; 21.02.2018
comment
Хм. Возможно, что VisualStudio, как правило, удается предупредить только в тех случаях, когда он может сделать вывод о том, что будет проблема преобразования знака. В моем примере с GCC выше компилятор может только сделать вывод, что может быть проблемой (в зависимости от значений, которые вы позже вводите в custom_make_unique). Так что, возможно, вы тоже столкнулись с ограничением Visual Studio. - person mindriot; 21.02.2018
comment
Действительно, заголовок memory временно снижает уровень предупреждения до 3 с помощью #pragma warning(push,3), что объясняет отсутствие предупреждения. Тем не менее, это не объясняет, почему custom_make_unique() также не работает. - person user2328447; 21.02.2018
comment
Спасибо за проверку. Я обновил свой ответ (к счастью, rextester.com предоставляет компилятор VC ++). - person mindriot; 22.02.2018