Hello, TL;DR: There has been a few review comments suggesting to shadow variable names in order to avoid the leading/trailing underscore in the name. In general, this only leads to stupid bugs that waste developers' time, we can eliminate these bugs and also the need to reevaluate if the names of variables need to be changed when adding new code. I'd like to at least discourage this practice from our codebase and in the best case scenario, turn *-Wshadow* on and make a pass over the codebase to remove shadowing variable names.
class Foo { public: // Prefer '_var' over 'var' here. Foo(int _var) : var(_var) {} int var; }; NOTE: The leading underscore is a non-standard naming scheme but that's a separate issue being tracked at https://issues.apache.org/jira/browse/MESOS-1046 This discussion arose from https://reviews.apache.org/r/30395/#comment115306, the following is the typical situation where the discussion arises. Review Request: class Foo { public: Foo(int _var) : var(_var) {} // Comment: Unnecessary leading underscore. int var; }; In this case, that is true. However, I think shadowing variable names should be avoided in general, especially between local variables (including function parameters) and member variables because the member variable declarations are typically lexically not in scope. Here are a few examples of how this can lead to problems in the future. 1) Present: > struct Obj > { > // Original author: "Eh, we don't need to refer to the member 'foo' so > this is fine." > Obj(Foo &&foo) > : foo(std::move(foo)) {} > Foo foo; > }; Future: > struct Obj > { > Obj(Foo &&foo) > : foo(std::move(foo)) { > // New author: "ok, let's do some stuff with member 'foo'!" > // oops... this is likely to segfault. > std::cout << foo << std::endl; > } > Foo foo; > }; 2) Present: > // Original author: "There's a member 'foo' but I don't need it for this > function." > Obj::F(const Foo &foo) { > // Do stuff with local 'foo' ... > } Future: > Obj::F(const Foo &foo) { > // Do stuff with local 'foo' ... > > // New author: (middle of debugging) "What state is (member) 'foo' > in...?" > // Local 'foo' gets printed instead, doesn't realize the problem until > they look above > // to find the shadowed variable and... fury. > std::cout << foo << std::endl; > } >From Cody Maloney: Some examples from the codebase of existing shadowing and problems which could be encountered working / refactoring around them. stout/json.hpp: inline Value convert(const picojson::value& value) { if (value.is<picojson::null>()) { return Null(); } else if (value.is<bool>()) { return Boolean(value.get<bool>()); } else if (value.is<picojson::value::object>()) { Object object; foreachpair (const std::string& name, const picojson::value& value, value.get<picojson::value::object>()) { object.values[name] = convert(value); } return object; } else if (value.is<picojson::value::array>()) { Array array; foreach (const picojson::value& value, value.get<picojson::value::array>()) { array.values.push_back(convert(value)); } return array; } else if (value.is<double>()) { return Number(value.get<double>()); } else if (value.is<std::string>()) { return String(value.get<std::string>()); } return Null(); } The foreachpair creates a second value which it uses in the assignment. Yes, this works right, but when scanning over the code if I don't catch that there was an additional definition embedded in the foreach... stout/flags.hpp: template <typename Flags, typename T1, typename T2> void FlagsBase::add( T1 Flags::*t1, const std::string& name, const std::string& help, const T2& t2) { Flags* flags = dynamic_cast<Flags*>(this); if (flags == NULL) { ABORT("Attempted to add flag '" + name + "' with incompatible type"); } else { flags->*t1 = t2; // Set the default. } Flag flag; flag.name = name; flag.help = help; flag.boolean = typeid(T1) == typeid(bool); flag.loader = lambda::bind( &MemberLoader<Flags, T1>::load, lambda::_1, t1, lambda::function<Try<T1>(const std::string&)>( lambda::bind(&parse<T1>, lambda::_1)), name, lambda::_2); flag.stringify = lambda::bind( &MemberStringifier<Flags, T1>, lambda::_1, t1); // Update the help string to include the default value. flag.help += help.size() > 0 && help.find_last_of("\n\r") != help.size() - 1 ? " (default: " // On same line, add space. : "(default: "; // On newline. flag.help += stringify(t2); flag.help += ")"; add(flag); } If the delegated add had been written inline (flags[flag.name] = flag), then things wouldn't work right... stout/os/killtre inline Try<std::list<ProcessTree> > killtree( pid_t pid, int signal, bool groups = false, bool sessions = false) { ... while (!queue.empty()) { pid_t pid = queue.front(); queue.pop(); ... } } If I need to modify the code inside the while loop to do something like exclude the original PID to resolve some sort of bug, I will have to rename a lot of variables for a small change in functionality. Most likely I won't notice that pid became even more local, and will first spend a while debugging why the code "Reads" right but doesn't do what it reads as. In short, I think we should opt to stay away from running into issues like this by not shadowing variable names. It keeps us away from running into stupid bugs that waste developer's time, and also eliminates the need to reevaluate if the names of variables need to be changed when adding new code. Thanks, MPark.