Избегать необходимости в #define с шаблонами выражений - PullRequest
1 голос
/ 03 мая 2011

При использовании следующего кода «hello2» не отображается, поскольку временная строка, созданная в строке 3, умирает до выполнения строки 4.Использование #define, как в строке 1, позволяет избежать этой проблемы, но есть ли способ избежать этой проблемы без использования #define?(Код C ++ 11 в порядке)

#include <iostream>
#include <string>

class C
{
public:
  C(const std::string& p_s) : s(p_s) {}
  const std::string& s;
};

int main()
{
  #define x1 C(std::string("hello1")) // Line 1
  std::cout << x1.s << std::endl; // Line 2

  const C& x2 = C(std::string("hello2")); // Line 3
  std::cout << x2.s << std::endl; // Line 4
}

Пояснение:

Обратите внимание, что я считаю, что Boost uBLAS хранит ссылки, поэтому я не хочусохранить копию.Если вы предлагаете мне хранить по значению, объясните, почему Boost uBLAS неверен и сохранение по значению не повлияет на производительность.

Ответы [ 4 ]

5 голосов
/ 03 мая 2011

Шаблоны выражений, которые сохраняются по ссылке, обычно делают это для повышения производительности, но с оговоркой они могут использоваться только как временные

Взятые из документации Boost.Proto (которая может бытьиспользуется для создания шаблонов выражений):

Примечание Проницательный читатель заметит, что объект y, определенный выше, останется с висячей ссылкой на временное int.В разновидностях высокопроизводительных приложений, использующих Proto-адреса, обычно строят и оценивают дерево выражений до того, как какие-либо временные объекты выходят из области видимости, поэтому часто возникает такая висячая ссылочная ситуация, но об этом, безусловно, нужно знать.,Proto предоставляет утилиты для глубокого копирования деревьев выражений, чтобы их можно было передавать как типы значений, не заботясь о висячих ссылках.

В исходном примере это означает, что вы должны сделать:

std::cout << C(std::string("hello2")).s << std::endl;

Таким образом, временное C никогда не переживает временное std::string.В качестве альтернативы вы можете сделать s не ссылочным членом, как указали другие.

Поскольку вы упомянули C ++ 11, в будущем я ожидаю, что деревья выражений будут храниться по значению, используя семантику перемещения, чтобы избежать дорогостоящего копирования иобертки, такие как std :: reference_wrapper, по-прежнему дают возможность хранения по ссылке.Это будет хорошо работать с auto.

Возможной версией C ++ 11 вашего кода:

class C
{
public:
    explicit
    C(std::string const& s_): s { s_ } {}

    explicit
    C(std::string&& s_): s { std::move(s_) } {}

    std::string const&
    get() const& // notice lvalue *this
    { return s; }

    std::string
    get() && // notice rvalue *this
    { return std::move(s); }

private:
    std::string s; // not const to enable moving
};

Это будет означать, что код, подобный C("hello").get(), выделит память только один раз,но все равно хорошо играть с

std::string clvalue("hello");
auto c = C(clvalue);
std::cout << c.get() << '\n'; // no problem here
4 голосов
/ 03 мая 2011

но есть ли способ избежать этой проблемы без использования #define?

Да.

Определите свой класс как: (не сохраняйте ссылку)

class C
{
public:
  C(const std::string & p_s) : s(p_s) {}
  const std::string s; //store the copy!
};

Сохраните копию!

Демонстрация: http://www.ideone.com/GpSa2


Проблема с вашим кодом состоит в том, что std::string("hello2") создает временный файл, и он остаетсяжив, пока вы находитесь в конструкторе C, и после этого временный объект уничтожается, но ваш объект x2.s все еще указывает на него (мертвый объект).

0 голосов
/ 03 мая 2011

Не глядя на BLAS, шаблоны выражений обычно интенсивно используют временные объекты типов, о которых вы даже не подозревали.Если Boost хранит подобные ссылки внутри своих, то они будут страдать от той же проблемы, что и здесь.Но до тех пор, пока эти временные объекты остаются временными, а пользователь не сохраняет их для дальнейшего использования, все в порядке, поскольку временные объекты, на которые они ссылаются, остаются живыми до тех пор, пока временные объекты сохраняются.Хитрость заключается в том, что вы выполняете глубокое копирование, когда промежуточный объект превращается в конечный объект, который хранит пользователь.Вы пропустили этот последний шаг здесь.

Короче говоря, это опасный шаг, который совершенно безопасен, если пользователь вашей библиотеки не делает глупостей.Я не рекомендовал бы использовать это, если у вас нет явной потребности, и вы хорошо знаете о последствиях.И даже тогда, может быть, есть лучшая альтернатива, я никогда не работал с шаблонами выражений в любом серьезном качестве.

Кроме того, поскольку вы пометили этот C ++ 0x, auto x = a + b; кажется, что это будет одна из тех "глупых" вещей, которые могут сделать пользователи вашего кода, чтобы сделать вашу оптимизацию опасной.

0 голосов
/ 03 мая 2011

После редактирования:

Хранение по ссылке опасно и иногда подвержено ошибкам .Это следует делать только тогда, когда вы на 100% уверены, что ссылка на переменную никогда не выйдет из области действия до ее смерти.

C ++ string очень оптимизирован.Пока вы не измените строковое значение, все будут ссылаться только на одну и ту же строку.Чтобы проверить это, вы можете перегрузить operator new (size_t) и поместить оператор отладки.Для нескольких копий одной и той же строки вы увидите, что выделение памяти будет происходить только один раз.

Определение класса должно храниться не по ссылке, а по значению как,

class C {
  const std::string s;  // s is NOT a reference now
};

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

class C {
  MyClass *p;
  C() : p (new MyClass()) {}  // just an example, can be allocated from outside also
 ~C() { delete p; }
};
...