Вернуть объект, у деструктора которого есть побочные эффекты - PullRequest
0 голосов
/ 14 февраля 2019

У нас есть класс, который помогает нам отправлять данные в telegraf / influenxdb (т. Е. Для мониторинга).Это выглядит примерно так:

class TelegrafSend {
  public:
    // // Constructor does some default stuff based on binary name and context.
    TelegrafSend();
    // Destructor sends the object.
    ~TelegrafSend();
    // Exists in a couple variants.  Probably could have been a template.
    void AddTag(const std::string& tag_name, const std::string& tag_value);
    // Same.
    void AddField(const std::string& field_name, const int field_value);
};

Для ясности, это выглядит так:

TelegrafSend TelegrafSend::AddField(const string& field_name, const int field_value) {
    fields_[field_name] = to_string(field_value);
    sent_ = false;
    return *this;
}

И это прекрасно работает:

TelegrafSend telegraf;
telegraf.AddTag("a_tag", "a_value");
telegraf.AddField(kTelegrafCount, 1);

и как этовыходит из области видимости, отправляется, что является хорошим поведением, потому что функция может добавить пару метрик при выполнении, и все выходы из функции вызывают отправку объекта.

Теперь у меня появилась умная идея:

class TelegrafSend {
  public:
    // // Constructor does some default stuff based on binary name and context.
    TelegrafSend();
    // Destructor sends the object.
    ~TelegrafSend();
    // Exists in a couple variants.  Probably could have been a template.
    TelegrafSend AddTag(const std::string& tag_name, const std::string& tag_value);
    // Same.
    TelegrafSend AddField(const std::string& field_name, const int field_value);
};

и поэтому я могу написать

TelegrafSend telegraf.AddTag("a_tag", "a_value").AddField(kTelegrafCount, 1);

Проблема здесь в том, что я создаю временные файлы, и поэтому, пока это работает в конце, каждое возвращение создает временное, которое получаетуничтожены и отправлены в телеграф.Это действительно неэффективно для influenxdb, даже не говоря о дурной практике в C ++.

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

Ответы [ 2 ]

0 голосов
/ 14 февраля 2019

Я бы реализовал в терминах std::optional и предоставил бы конструктор перемещения, чтобы обеспечить автоматический индикатор «валидности» во время деструктора.

Обратите внимание, что перемещение из необязательного параметра не приводит к его очистке, оно только перемещает содержимое, поэтому необходимо сбросить необязательное значение.

Полный пример:

#include <optional>
#include <string>
#include <iostream>

struct TelegrafSend 
{
    // // Constructor does some default stuff based on binary name and context.
    TelegrafSend();

    TelegrafSend(TelegrafSend&& other);
    TelegrafSend(TelegrafSend const& ) = delete;
    TelegrafSend& operator=(TelegrafSend const& ) = delete;
    TelegrafSend& operator=(TelegrafSend && ) = delete;

    // Destructor sends the object.
    ~TelegrafSend();

    TelegrafSend& AddTag(const std::string& tag_name, const std::string& tag_value);
    TelegrafSend& AddField(const std::string& field_name, const int field_value);

private:

    struct Impl
    {
        std::string narrative;

        void more(std::string const& s)
        {
            if (!narrative.empty())
                narrative += '\n';
            narrative += s;
        }

        void emit()
        {
            if (narrative.empty())
                std::cout << "{empty}\n";
            else
                std::cout << narrative << '\n';
        }
    };

    std::optional<Impl> impl_;

};

TelegrafSend::TelegrafSend() 
: impl_(Impl())
{

}

TelegrafSend::TelegrafSend(TelegrafSend&& other)
: impl_(std::move(other.impl_))
{
    other.impl_.reset();
}

TelegrafSend::~TelegrafSend() 
{
    if(impl_.has_value())
        impl_->emit();
}

TelegrafSend& TelegrafSend::AddTag(const std::string& tag_name, const std::string& tag_value)
{
    auto s = "Tag : " + tag_name + " : " + tag_value;
    impl_->more(s);
    return *this;
}

TelegrafSend& TelegrafSend::AddField(const std::string& field_name, const int field_value)
{
    auto s = "Field : " + field_name + " : " + std::to_string(field_value);
    impl_->more(s);
    return *this;
}


auto test(TelegrafSend ts = {}) -> TelegrafSend
{
    ts.AddTag("foo", "bar").AddField("baz", 6);
    return ts;
}

int main()
{
    {
        test(), std::cout << "hello\n";
    }

    std::cout << "world\n";
}

ожидаетсявывод:

hello
Tag : foo : bar
Field : baz : 6
world

https://coliru.stacked -crooked.com / a / 755d3d161b9d48b3

0 голосов
/ 14 февраля 2019

Вы должны вернуть ссылку на себя в этих методах вместо создания нового объекта.
Реализация конструктора перемещения также является возможностью для рассмотрения.

class TelegrafSend {
  public:
    TelegrafSend();
    ~TelegrafSend();
    TelegrafSend(const TelegrafSend&) = delete;
    TelegrafSend& operator = (const TelegrafSend&) = delete;
    TelegrafSend(TelegrafSend&&); // possibly = delete;
    TelegrafSend& operator = (TelegrafSend&&); // possibly = delete;

    // Exists in a couple variants.  Probably could have been a template.
    TelegrafSend& AddTag(const std::string& tag_name, const std::string& tag_value)
    {
        /*..*/
        return *this;
    }
    // Same.
    TelegrafSend& AddField(const std::string& field_name, const int field_value)
    {
        /*..*/
        return *this;
    }

};

И затем вы можете использовать:

TelegrafSend{}.AddTag("a_tag", "a_value").AddField(kTelegrafCount, 1);
...