2

We are playing around with clang-tidy on our builds and I have come across something that I don't fully understand about it's behavior. I will put the code first and my questions afterwards.

If I use the following code:

#include <iostream>

using namespace std;

class Param {
public:
    Param() {cout << "constructor" << endl;}
    ~Param() {cout << "destructor" << endl;}
    Param(const Param &other) {cout << "copy constructor" << endl;}
    Param(Param&& other) {cout << "move constructor" << endl;}
    Param& operator=(const Param& other) {cout << "copy assignment" << endl; return *this; }
    Param& operator=(Param&& other) {cout << "Param move assignment" << endl; return *this; }
};

class A {
public:
    A(const Param& param) : m_param(param){}
    Param m_param;
};

int main() {
    const Param param;
    A a(param);
}

and this CMake file:

cmake_minimum_required(VERSION 3.2)
project(clang)
add_executable(clang main.cpp)
set_property(TARGET clang PROPERTY CXX_STANDARD 14)

the generated binary will output:

constructor
copy constructor
destructor
destructor

If I then enable clang-tidy on the CMake file like so:

cmake_minimum_required(VERSION 3.2)
project(clang)
add_executable(clang main.cpp)
set_property(TARGET clang PROPERTY CXX_STANDARD 14)
set_property(TARGET clang PROPERTY CXX_CLANG_TIDY "/usr/bin/clang-tidy;-checks=modernize-*;-fix")

compilation will print the following:

/.../main.cpp:17:7: warning: pass by value and use std::move [modernize-pass-by-value]
    A(const Param& param) : m_param(param){}
      ^
/.../main.cpp:2:1: note: FIX-IT applied suggested code changes
^
/.../main.cpp:17:7: note: FIX-IT applied suggested code changes
    A(const Param& param) : m_param(param){}
      ^
/.../main.cpp:17:37: note: FIX-IT applied suggested code changes
    A(const Param& param) : m_param(param){}
                                    ^
/.../main.cpp:17:42: note: FIX-IT applied suggested code changes
    A(const Param& param) : m_param(param){}
                                         ^

and code gets modified to:

class A {
public:
    A(Param  param) : m_param(move(param)){}
    Param m_param;
};

if I then run the binary I get:

constructor
copy constructor
move constructor
destructor
destructor
destructor

Questions:

1 - In both situations the copy constructor is needed:

  • copying to the member variable
  • copying in the constructor argument

But why does clang-tidy "thinks" that the extra move constructor actually helps? doesn't that only add extra computational cost?

http://clang.llvm.org/extra/clang-tidy/checks/modernize-pass-by-value.html

Documentation says that it assumes that move is cheap (which makes sense), but aren't you adding that to the copy anyway?

EDIT: seems like this question got answered here. I am still curious about question 2.

2 - Assuming that I misunderstood something and clang-tidy is correct, what should one do when subclassing? pass by value twice?

class A {
public:
    A(Param  param) : m_param(move(param)){}
    Param m_param;
};

class B : public A {
public:
    B(Param param) : A(move(param)){}
};
Community
  • 1
  • 1
Mac
  • 3,397
  • 3
  • 33
  • 58
  • 1
    "*doesn't that only add extra computational cost?*" It only adds extra computational cost if you're copying the object. In your original code, if you pass a prvalue, you get a copy. In the new code, if you pass a prvalue, then it would move it twice. For a *real* `param` that actually did something in those user-provided copy/move constructors, this would likely be faster. Consider a `std::vector`, for example. – Nicol Bolas Sep 07 '16 at 18:10

0 Answers0