jeudi 1 juillet 2021

Avoid using of boost optional in case there is no need to copy object

Note: It's actually a continuation of question: G++-11 destruction order changed from G++9

We discovered, that we use UB thing (order of temporary objects constructors) everywhere in code, so we decide to slightly rewrite one of the code pieces. Suppose we have the following code in one of the third-party libraries (SOCI actually), this library doesn't use move semantics in the version, that we use, so, I just added it to it (look on comments):

#include <iostream>
#include <string>

#include <boost/optional.hpp>
/* trick namespace was added by me */
namespace trick
{

template<typename T>
struct maybe_holder
{
public:
   maybe_holder() = default;
   maybe_holder(T&& v) : value(v)
   {
   }

   boost::optional<T> value;
};

}

struct base_use_type
{
    base_use_type(void* v) : value(v) {}
    void* value;
};

/* NOTE: only base_use_type was parent of this class */
template <typename T>
class use_type : private trick::maybe_holder<T>, public base_use_type
{
public:
    use_type(T& t) :
    base_use_type(&t)
    {
        std::cout << "called ref ctor" << std::endl;
    }
    
    use_type(T const& t) :
    base_use_type(&const_cast<T&>(t))
    {
        std::cout << "called cref ctor" << std::endl;
    }
    /* NOTE: this was added by me */
    use_type(T&& t)
        : trick::maybe_holder<T>(std::forward<T>(t)), base_use_type(&trick::maybe_holder<T>::value.get())
   {
        std::cout << "called rvalue ctor" << std::endl;
   }
};

template <typename T>
use_type<T> do_use(T & t)
{
    return (use_type<T>(t));
}

template <typename T>
use_type<T> do_use(T const & t)
{
    return (use_type<T>(t));
}
/* NOTE: this was added by me */
template<typename T>
use_type<T> do_use(T && t)
{
    return (use_type<T>(std::forward<T>(t)));
}

int main()
{
    do_use(std::string("abc"));
    std::string s;
    do_use(s);
    const std::string ss = "11";
    do_use(ss);
}

live example

We use such kind of code in real code with something like:

   class A {
   public:
      A(const std::string& a, const std::string& b, const std::string& c) :
         a_(a), b_(b), c_(c)
      {
      }
      std::string get_a() const { return a_; }
      std::string get_b() const { return b_; }
      std::string get_c() const { return c_; }
   private:
      std::string a_;
      std::string b_;
      std::string c_;
   };
   const std::string a = std::string(500, 'a');
   const std::string b = std::string(500, 'b');
   const std::string c = std::string(500, 'c');

   A o(a, b, c);
   auto& sql = get_session();
   for (int i = 0; i < 10; ++i)
   {
      sql << "insert into temporary_strings_table (a, b, c) values (:a, :b, :c)",
          do_use(o.get_a()),
          do_use(o.get_b()),
          do_use(o.get_c());
   }

Note: it's just a simple example. It's connected with SQL, so, we cannot just make a copy every time use is called (cause, there are out variables in PL/SQL, or for example returning into in Oracle).

I'm trying to understand, is there is any way to avoid memory overhead of boost::optional in maybe_holder? Thanks in advance.

Aucun commentaire:

Enregistrer un commentaire