首页 > 解决方案 > 什么是引用复制构造函数?

问题描述

根据我构造Log对象的方式,我收到一个编译器错误,指出我正在尝试引用已删除的函数(复制构造函数):

C:\Projects\Logger\src\Logger.cpp(34,1): 错误 C2280: 'Logger::Log::Log(const Logger::Log &)': 试图引用已删除的函数 [C:\Projects \Logger\build\Logger.vcxproj]

预计我的 Log 对象已隐式删除了复制构造函数,因为:

T 具有无法复制的非静态数据成员(已删除、不可访问或不明确的复制构造函数);

这是有道理的,因为std::ofstream它的复制构造函数已删除

我无法弄清楚为什么要调用复制构造函数。使用调用复制构造函数的赋值运算符进行构造是什么?我正在使用 MSVC 进行编译,是否有一些我没有使用的编译器标志不能优化某些行为?例如构造临时对象然后将构造复制到命名对象中foo

如下代码块所示,只有一个构造函数定义。

#include <iostream>
#include <string>
#include <fstream>
#include "Logger.h"
#include "LoggerConfig.h"

Logger::Log::Log(std::string file) : filename{ file }
{
    logFile = std::ofstream(filename, std::ios::out);
    if (logFile.is_open())
    {
        logFile << "This is a log.\n";
    }
    else
    {
        std::cout << "Unable to open filename: " << filename << '\n';
    }

}

Logger::Log::~Log()
{
    // Wait and take write mutex

    // Close file
}

int main()
{
    // report version
    std::cout << " Version " << LOGGER_VERSION_MAJOR << "."
              << LOGGER_VERSION_MINOR << std::endl;

    Logger::Log foo = Logger::Log::Log("sample.log"); // C2280: attemping to reference a deleted function
    //Logger::Log foo("sample.log"); // Works!
    //Logger::Log foo{"sample.log"}; // Works!

    foo.Write(Logger::Log::Level::INFO, "Testing", 123, "hahaha");

    return 0;
}

类接口logger.h如下:

#pragma once

namespace Logger
{
    class Log
    {
    private:
        std::string filename;
        std::ofstream logFile;

    public:
        enum class Level
        {
            DEBUG,
            INFO,
            WARNING,
            ERROR
        };

        Log(std::string file);
        ~Log();

        template<typename T>
        void Write(Level lvl, T arg)
        {
            logFile << arg;
            return;
        }

        template<typename T, typename... Args>
        void Write(Level lvl, T firstArg, Args... args)
        {
            logFile << firstArg;
            Write(lvl, args...);
            return;
        }
    };
}

标签: c++

解决方案


这不仅仅是关于不必要的副本和 C++17,我们知道

   Logger::Log foo("sample.log"); 

工作正常,但为什么这会标记 C2280 错误

   Logger::Log foo = Logger::Log::Log("sample.log");

问题出在类中的 ofstream 中,我得到了你的代码并编译了它,我对 ofstream 对象有疑问,我删除了它,它工作正常我也把它变成了一个指针也工作正常,然后我尝试了这个代码:

 std::ofstream s =  std::ofstream("sample.log", std::ios::out);
 std::ofstream k;
 k = s;

我收到此错误 E1776 函数“std::basic_ofstream<_Elem, _Traits>::operator=(const std::basic_ofstream<_Elem, _Traits> &) [with _Elem=char, _Traits=std::char_traits]”(声明于无法引用“C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.25.28610\include\fstream”的第 1080 行——这是一个已删除的函数

这意味着 ofstream 阻止复制构造函数和 =operator 以避免访问相同的数据(文件),当您通过此行调用复制构造函数时,您的代码就是这种情况

 Logger::Log foo = Logger::Log::Log("sample.log");

隐式使用默认的复制构造函数,它使用 = 运算符为您拥有的每个对象或原始类型逐个字段复制,而 ofstream 就是这种情况,它阻止 = 运算符然后编译器标记为调用已删除的函数错误 -

要解决这个问题,您可以使用原始指针或智能指针声明 ofstream 指针并管理内存,或者您也可以使用自定义创建移动构造函数版本——这也会删除类中的默认复制构造函数——并尽量避免使用不同的ofstreams访问相同的数据。


推荐阅读