What is referencing the copy constructor?

Viewed 144

Depending on how I construct my Log object, I get a compiler error stating that I'm trying to reference a deleted function (the copy constructor):

C:\Projects\Logger\src\Logger.cpp(34,1): error C2280: 'Logger::Log::Log(const Logger::Log &)': attempting to reference a deleted function [C:\Projects\Logger\build\Logger.vcxproj]

It's expected that my Log object has implicitly deleted the copy constructor because:

T has non-static data members that cannot be copied (have deleted, inaccessible, or ambiguous copy constructors);

This makes sense due to std::ofstream which has its copy constructor deleted.

What I can't figure out is why the copy constructor is being called at all. What is it about constructing with the assignment operator that calls the copy constructor? I'm compiling with MSVC, is there some compiler flag I'm not using that doesn't optimize certain behavior? E.g. Construct temporary object then copy construct into named object foo?

There is only one constructor definition as seen in the code block below.

#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;
}

Class interface logger.h below:

#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;
        }
    };
}
3 Answers

It's not just about the unnecessary copy and C++17, we know that

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

works fine but why this flags C2280 Error

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

the problem is in ofstream that is in the class, I got your code and I compiled it, I had a doubt about ofstream object, I deleted it, and it works fine also I made it a pointer also works fine, then I tried this code:

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

I got this error E1776 function "std::basic_ofstream<_Elem, _Traits>::operator=(const std::basic_ofstream<_Elem, _Traits> &) [with _Elem=char, _Traits=std::char_traits]" (declared at line 1080 of "C:\Program Files (x86)\Microsoft Visual Studio\2019\Enterprise\VC\Tools\MSVC\14.25.28610\include\fstream") cannot be referenced -- it is a deleted function

which means ofstream prevent copy constructor and =operator to avoid the access to the same data (file) and this is the case in your code, when you call the copy constructor by this line

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

implicitly using the default copy constructor, it copies field by field using =operator for each object you have or a primitive type, and this is the case with ofstream that prevents the =operator then the compiler flags with calling a deleted function error--

to solve this you can declare ofstream pointer using raw pointers or smart ones and manage the memory or you can also create your move constructor version with your customization --which will delete the default copy constructor in your class also-- and try to avoid the access to the same data using different ofstreams.

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

This line first create a Log and copy it to foo.

You must call the constructor like this Logger::Log foo("sample.log") because what you're doing will create an unnecessary copy, which compilers before C++17 don't like.

Related