如何在C ++中使用Move语义进行运算符重载? 优雅

如何解决如何在C ++中使用Move语义进行运算符重载? 优雅

class T {
    size_t *pData;          // Memory allocated in the constructor
    friend T operator+(const T& a,const T& b);
};
T operator+(const T& a,const T& b){        // Op 1
        T c;                            // malloc()
        *c.pData = *a.pData + *b.pData;
        return c;
}

T do_something(){
    /* Implementation details */
    return T_Obj;
}

带有动态内存的简单class T。考虑

T a,b,c;
c = a + b;                                      // Case 1
c = a + do_something(b);            // Case 2
c = do_something(a) + b;            // Case 3
c = do_something(a) + do_something(b);           // Case 4
  • 案例1使用1个malloc()
  • 案例2使用2个malloc()
  • 案例3使用2个malloc()
  • 案例4使用3个malloc()

我们可以通过附加定义来做得更好,

T& operator+(const T& a,T&& b){           // Op 2
                    // no malloc() steeling data from b rvalue
        *b.pData = *a.pData + *b.pData;
        return b;
}

案例2现在仅使用1个malloc(),但是案例3呢?我们需要定义Op 3吗?

T& operator+(T&& a,const T& b){            // Op 3
                    // no malloc() steeling data from a rvalue
        *b.pData = *a.pData + *b.pData;
        return b;
}

此外,如果我们确实定义了Op 2和Op 3,考虑到右值引用可以绑定到左值引用这一事实,则编译器现在有两个在情况4中可以调用的同样合理的函数定义

T& operator+(const T& a,T&& b);        // Op 2 rvalue binding to a
T& operator+(T&& a,const T& b);        // Op 3 rvalue binding to b

编译器会抱怨函数调用不明确,定义Op 4是否可以解决编译器的函数调用问题?因为我们在Op 4中没有获得任何额外的性能

T& operator+(T&& a,T&& b){          // Op 4
                    // no malloc() can steel data from a or b rvalue
        *b.pData = *a.pData + *b.pData;
        return b;
}

对于Op 1,Op 2,Op 3和Op 4,我们拥有

  • 情况1:1个malloc(称为操作1)
  • 情况2:1个malloc(称为操作2)
  • 情况3:1个malloc(称为操作3)
  • 案例4:1个malloc(称为Op 4)

如果我的理解是正确的,那么每个操作员将需要四个功能签名。这似乎有点不对劲,因为每个操作员都有很多样板和代码重复。我想念什么吗?有没有达到相同目的的优雅方法?

解决方法

最好不要尝试使用operator+(或任何二进制运算符)窃取资源,而设计一个更合适的以某种方式重用数据的方法 1 。如果不是唯一的方法,这应该是您的API惯用的构建方式(如果您想完全避免此问题)。


在C ++中,像operator+这样的二进制运算符通常期望/习惯于返回一个不同的对象,而不会改变其任何输入。定义operator+以与Lvalues一起使用Rvalues会引入一个非常规的接口,这会使大多数C ++开发人员感到困惑。

考虑您的案例4 示例:

c = do_something(a) + do_something(b);           // Case 4

ab是哪个资源被盗?如果a的大小不足以支持b所需的结果怎么办(假设它使用了调整大小的缓冲区)?没有一般的情况可以使它成为一个简单的解决方案。

此外,无法区分API上的不同类型的Rvalue,例如Xvalues(std::move的结果)和PRvalues(返回值的函数的结果)。这意味着您可以调用相同的API:

c = std::move(a) + std::move(b);

在这种情况下,根据您的上述启发式方法,a b中只有一个会被盗用资源,这很奇怪。这将导致基础资源的生存期未扩展c,这可能与开发人员的直觉相违背(例如,考虑到a中的资源或b具有明显的副作用,例如日志记录或其他系统交互)

注意:值得注意的是,C ++中的std::string存在相同的问题,即operator+效率低下。重用缓冲区的一般建议是在这种情况下使用operator+=


1 一个更好的解决方案是以某种方式创建正确的构建方法,并始终如一地使用它。这可以通过命名功能强大的函数,某种适当的builder类或仅使用operator+=

这样的复合运算符来实现。

这甚至可以通过模板帮助程序函数完成,该函数将一系列参数折叠为+=串联系列。假设此版本位于或更高版本中,则可以轻松完成此操作:

template <typename...Args>
auto concat(Args&&...args) -> SomeType
{
    auto result = SomeType{}; // assuming default-constructible

    (result += ... += std::forward<Args>(args));
    return result;
}
,

从技术上讲这是可行的。但您可能应该考虑进行设计更改。 该代码只是一个POC。 它有一个UB,但可以在gcc和clang上使用...

#include <type_traits>
#include <iostream>

    struct T {
        T()
         : pData (new size_t(1)),owner(true)
        { 
            
            std::cout << "malloc" << std::endl; 
        }
        ~T()
        {
            if (owner)
            {
                delete pData;
            }
        }
        T(const T &) = default;
        size_t *pData;          // Memory allocated in the constructor              
        bool   owner;           // pData ownership
        
        template <class T1,class T2>
        friend T operator+(T1 && a,T2 && b){
            
            T c(std::forward<T1>(a),std::forward<T2>(b));
            *c.pData = *a.pData + *b.pData; //UB but works
            return c;
        }
        
        private:
        template <class T1,class T2>
        T(T1 && a,T2 && b) : owner(true)
        {  
            static_assert(std::is_same_v<T,std::decay_t<T1>> && std::is_same_v<T,std::decay_t<T2>>,"only type T is supported");
            if (!std::is_reference<T1>::value)
            {
                pData = a.pData;
                a.owner = false;
                std::cout << "steal data a" << std::endl;   
            }
            else if (!std::is_reference<T2>::value)
            {
                pData = b.pData;
                b.owner = false;
                std::cout << "steal data b" << std::endl;   
            }
            else
            {
                std::cout << "malloc anyway" << std::endl;
                pData = new size_t(0);
            }            
        }
    };

int main()
{
    T a,b;
    T r = a +b; // malloc
    std::cout << *r.pData << std::endl;
    T r2 = std::move(a) + b; // no malloc
    std::cout << *r2.pData << " a: " << *a.pData << std::endl;
    T r3 = a + std::move(b); // no malloc
    std::cout << *r3.pData << " a: " << *a.pData << " b: " << *b.pData << std::endl;
    return 0;
}
,

这是高性能和优雅的,但是使用了宏。


#include <type_traits>
#include <iostream>

#define OPERATOR_Fn(Op)         \
template<typename T1,typename T2>          \
friend auto operator Op (T1&& a,T2&& b)          \
           -> typename std::enable_if<std::is_same<std::decay_t<T1>,std::decay_t<T2>>::value,std::decay_t<T1>>::type \
{                                                           \
    constexpr bool a_or_b = !std::is_reference<T1>::value;            \
    std::decay_t<T1> c((a_or_b? std::forward<T1>(a) : std::forward<T2>(b)));  \
            \
   *c.pData = *c.pData Op (!a_or_b? *a.pData : *b.pData);           \
    return c;                           \
}                   \

struct T {
    T(): pData(new size_t(1)) {std::cout << "malloc" << '\n';}
    ~T() {delete pData;}
    T(const T& b): pData(new size_t(1)) { *pData = *b.pData; std::cout << "malloc" << '\n';}
    T(T&& b){
        pData = b.pData;
        b.pData = nullptr;
        std::cout<< "move constructing" << '\n';
    }

    size_t *pData;          // Memory allocated in the constructor              

    OPERATOR_Fn(+);
    OPERATOR_Fn(-);
    OPERATOR_Fn(&);
    OPERATOR_Fn(|);
};

您可以通过定义以下内容来简化type_traits表达式,以使代码更具可读性

template <typename T1,typename T2>
struct enable_if_same_on_decay{
    static constexpr bool value = std::is_same<std::decay_t<T1>,std::decay_t<T2>>::value;
    typedef std::enable_if<value,std::decay_t<T>>::type type;
};

template <typename T1,typename T2>
using enable_if_same_on_decay_t = typename enable_if_same_on_decay<T1,T2>::type;

复杂的type_traits表达式

-> typename std::enable_if<std::is_same<std::decay_t<T1>,std::decay_t<T1>>::type

简单地变成

-> enable_if_same_on_decay_t<T1,T2>

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-