【问题标题】:What is referencing the copy constructor?什么是引用复制构造函数?
【发布时间】:2020-08-06 00:55:59
【问题描述】:

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

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

我的 Log 对象应该有 implicitly deleted 复制构造函数,因为:

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

这是有道理的,因为std::ofstream 有其copy constructor deleted

我不明白为什么要调用复制构造函数。使用调用复制构造函数的赋值运算符进行构造是什么?我正在使用 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;
        }
    };
}

【问题讨论】:

  • 能看到类界面吗?它可能会有所帮助
  • 添加类接口
  • Logger::Log foo = Logger::Log::Log("sample.log") 语义上(在 C++17 之前)使用字符串创建一个临时的 Logger::Log,然后使用复制构造函数用该临时值初始化 foo - 即使编译器省略该副本需要诊断复制构造函数的语义使用。从 C++17 开始,临时文件将被省略。在 C++17 之前,修复方法是将定义更改为 Logger::Log foo ("sample.log")
  • @Peter 应该作为答案发布。

标签: c++


【解决方案1】:

这不仅仅是不必要的副本和 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");

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

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

【讨论】:

    【解决方案2】:
    Logger::Log foo = Logger::Log::Log("sample.log");
    

    这一行先创建一个Log,复制到foo

    【讨论】:

      【解决方案3】:

      你必须像这样调用构造函数 Logger::Log foo("sample.log") 因为你所做的会创建一个不必要的副本,这是 C++17 之前的编译器不喜欢的。

      【讨论】:

        猜你喜欢
        • 2017-09-04
        • 2015-06-10
        • 2012-06-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多