Как передать указатель на целое число без временной переменной? - PullRequest
3 голосов
/ 07 мая 2019

В ряде языков из моего прошлого есть способ передать целочисленную константу / литерал в качестве ссылки, чтобы избежать ненужного создания чрезвычайно короткоживущих переменных для функций.Отличным примером является переменная повторного использования вызова setsockopt.Например,

int reuseVal = 1;    
setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &reuseVal, sizeof (reuseVal));

Некоторые языки позволяют выполнять следующие действия:

setsockopt(s, SOL_SOCKET, SO_REUSEADDR, %ref(1), sizeof (int));

Существует ли аналогичный метод в C ++?

Ответы [ 3 ]

2 голосов
/ 07 мая 2019

Нет ничего встроенного в C ++, который делает то, что вы хотите, но его легко создать самостоятельно (с дополнительной регистрацией для ясности):

template <typename T>
class holster
{
public:
    using value_type = T;

    template <typename... U>
    holster(U&&... args)
        : _val(std::forward<U>(args)...)
    {
        std::cout << "CTOR: holster" << std::endl;
    }

    ~holster()
    {
        std::cout << "DTOR: holster" << std::endl;
    }

    value_type* ptr() { return &_val; }

private:
    value_type _val;
};

Использование этого типа довольно просто:

struct thing
{
    thing()
    {
        std::cout << "CTOR: thing" << std::endl;
    }

    ~thing()
    {
        std::cout << "DTOR: thing" << std::endl;
    }
};

void foo(thing*)
{
    std::cout << "in foo" << std::endl;
}

int main()
{
    foo(holster<thing>().ptr());
    return 0;
}

Чтобы вернуться к исходному примеру:

setsockopt(s, SOL_SOCKET, SO_REUSEADDR, holster<int>(1).ptr(), sizeof (int));

Почему это работает?Малоизвестная особенность времен жизни C ++ состоит в том, что любой временный объект, созданный для передачи функции, продлевается на весь срок действия функции.Референт (holster::_val) имеет гарантию для продолжения существования до возврата foo и будет должным образом уничтожен до оценки следующего полного выражения (в данном случае до ;).

§6.6.7 / 4 [class.teilitary]

Когда реализация вводит временный объект класса, имеющий нетривиальный конструктор ( [class.default.ctor] , [class.copy.ctor] ), он должен обеспечить вызов конструктора для временного объекта.Точно так же деструктор должен быть вызван для временного с нетривиальным деструктором ( [class.dtor] ). Временные объекты уничтожаются как последний шаг при оценке полного выражения ( [intro.execution] ), которое (лексически) содержит точку, в которой они были созданы. Это верно, даже еслиэта оценка заканчивается выбрасыванием исключения.Вычисления значений и побочные эффекты уничтожения временного объекта связаны только с полным выражением, а не с каким-либо конкретным подвыражением.

1 голос
/ 07 мая 2019

Вы не можете сделать это напрямую.В C вы можете использовать составной литерал (например, (int []){ 1 }), но он недоступен в C ++.

Однако вы можете написать вспомогательную функцию следующим образом:

template<typename T>
struct temp_holder {
    T data;
    explicit temp_holder(T x) : data(x) {}
    T *ptr() { return &data; }
    const T *ptr() const { return &data; }
};

template<typename T>
temp_holder<T> make_temporary(T x) {
    temp_holder<T> tmp(x);
    return tmp;
}

А теперьВы можете сделать:

setsockopt(s, SOL_SOCKET, SO_REUSEADDR, make_temporary(1).ptr(), sizeof (int));

В моих тестах с g ++ это генерирует код, идентичный версии int reuseVal = 1 / &reuseVal на любом уровне оптимизации выше 0 (т. е. -O, -O2 или -O3).

Если вы не хотите определять новый тип и функцию, вы можете слегка злоупотреблять стандартной библиотекой:

#include <memory>

setsockopt(s, SOL_SOCKET, SO_REUSEADDR, std::make_unique<int>(1).get(), sizeof (int));

Недостатком этого подхода является то, что он работаетчерез динамическое выделение памяти (плюс (исключение-безопасное) освобождение), поэтому он может быть медленнее, чем пользовательский тип (или просто объявление временной переменной).

0 голосов
/ 07 мая 2019

setsockopt() требует указатель на выделенную переменную.Независимо от того, размещается ли эта переменная статически или динамически, это не имеет значения, если она имеет доступный адрес памяти, который можно получить во время выполнения и затем разыменовать для доступа к данным по этому адресу.

Ваш исходный кодделает точно это.Он объявляет локальную переменную, которая размещается в автоматической памяти (то есть в стеке вызовов вызывающего потока), затем использует оператор address-of &, чтобы получить адрес памяти этой переменной, и передает этот адрес в setsockopt(),Вещи не становятся намного проще, чем это.

Другие ответы, которые вам дали, это просто более сложные вариации этой же темы - присвоение значения переменной и затем получение адреса этой переменной.

Чтобы сохранить УЖЕ КРАЙНИЕ МИНИМАЛЬНЫЕ накладные расходы на объявление переменной примитивного типа и заполнение ее данными, вы можете выполнить это статически во время компиляции, используя глобальную переменную, которая инициализируется один раз при запуске программы и повторно используется при необходимости, например:

const int cReuseAddrOn = 1;

void setReuseAddrOn(int s)
{
    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &cReuseAddrOn, sizeof (cReuseAddrOn));
} 

Я бы предположил, что такие вещи, как %ref(1), в других языках, вероятно, будут реализованы аналогично этому, создавая ссылку на временно размещенную переменную или литеральную константу многократного использования.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...