cpp11-migrate: Add Pass-By-Value Transform

Currently only constructor parameters stored in class-local storage are modified
to make use of the pass-by-value idiom but this is a base that can be be further
improved to handle more situations.

llvm-svn: 189363
This commit is contained in:
Guillaume Papin 2013-08-27 16:11:26 +00:00
parent fe48aaf1a4
commit dcdae946f3
15 changed files with 868 additions and 0 deletions

View File

@ -139,6 +139,12 @@ public:
bool isFileModifiable(const clang::SourceManager &SM,
const clang::SourceLocation &Loc) const;
/// \brief Whether a transformation with a risk level of \p RiskLevel is
/// acceptable or not.
bool isAcceptableRiskLevel(RiskLevel RiskLevel) const {
return RiskLevel <= GlobalOptions.MaxRiskLevel;
}
/// \brief Called before parsing a translation unit for a FrontendAction.
///
/// Transform uses this function to apply file overrides and start

View File

@ -0,0 +1,80 @@
//===-- PassByValue.cpp ---------------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file provides the implementation of the ReplaceAutoPtrTransform
/// class.
///
//===----------------------------------------------------------------------===//
#include "PassByValue.h"
#include "PassByValueActions.h"
#include "PassByValueMatchers.h"
using namespace clang;
using namespace clang::tooling;
using namespace clang::ast_matchers;
int PassByValueTransform::apply(
FileOverrides &InputStates, const tooling::CompilationDatabase &Database,
const std::vector<std::string> &SourcePaths) LLVM_OVERRIDE {
ClangTool Tool(Database, SourcePaths);
unsigned AcceptedChanges = 0;
unsigned RejectedChanges = 0;
MatchFinder Finder;
ConstructorParamReplacer Replacer(getReplacements(), AcceptedChanges,
RejectedChanges,
/*Owner=*/ *this);
Finder.addMatcher(makePassByValueCtorParamMatcher(), &Replacer);
// make the replacer available to handleBeginSource()
this->Replacer = &Replacer;
setOverrides(InputStates);
if (Tool.run(createActionFactory(Finder))) {
llvm::errs() << "Error encountered during translation.\n";
return 1;
}
setAcceptedChanges(AcceptedChanges);
setRejectedChanges(RejectedChanges);
return 0;
}
bool PassByValueTransform::handleBeginSource(CompilerInstance &CI,
llvm::StringRef Filename) {
assert(Replacer && "Replacer not set");
IncludeManager.reset(new IncludeDirectives(CI));
Replacer->setIncludeDirectives(IncludeManager.get());
return Transform::handleBeginSource(CI, Filename);
}
struct PassByValueFactory : TransformFactory {
PassByValueFactory() {
// Based on the Replace Auto-Ptr Transform that is also using std::move().
Since.Clang = Version(3, 0);
Since.Gcc = Version(4, 6);
Since.Icc = Version(13);
Since.Msvc = Version(11);
}
Transform *createTransform(const TransformOptions &Opts) LLVM_OVERRIDE {
return new PassByValueTransform(Opts);
}
};
// Register the factory using this statically initialized variable.
static TransformFactoryRegistry::Add<PassByValueFactory>
X("pass-by-value", "Pass parameters by value where possible");
// This anchor is used to force the linker to link in the generated object file
// and thus register the factory.
volatile int PassByValueTransformAnchorSource = 0;

View File

@ -0,0 +1,74 @@
//===-- PassByValue.h -------------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file provides the declaration of the PassByValueTransform
/// class.
///
//===----------------------------------------------------------------------===//
#ifndef CPP11_MIGRATE_PASS_BY_VALUE_H
#define CPP11_MIGRATE_PASS_BY_VALUE_H
#include "Core/Transform.h"
#include "Core/IncludeDirectives.h"
class ConstructorParamReplacer;
/// \brief Subclass of Transform that uses pass-by-value semantic when move
/// constructors are available to avoid copies.
///
/// When a class constructor accepts an object by const reference with the
/// intention of copying the object the copy can be avoided in certain
/// situations if the object has a move constructor. First, the constructor is
/// changed to accept the object by value instead. Then this argument is moved
/// instead of copied into class-local storage. If an l-value is provided to the
/// constructor, there is no difference in the number of copies made. However,
/// if an r-value is passed, the copy is avoided completely.
///
/// For example, given:
/// \code
/// #include <string>
///
/// class A {
/// std::string S;
/// public:
/// A(const std::string &S) : S(S) {}
/// };
/// \endcode
/// the code is transformed to:
/// \code
/// #include <string>
///
/// class A {
/// std::string S;
/// public:
/// A(std::string S) : S(std::move(S)) {}
/// };
/// \endcode
class PassByValueTransform : public Transform {
public:
PassByValueTransform(const TransformOptions &Options)
: Transform("PassByValue", Options), Replacer(0) {}
/// \see Transform::apply().
virtual int apply(FileOverrides &InputStates,
const clang::tooling::CompilationDatabase &Database,
const std::vector<std::string> &SourcePaths) LLVM_OVERRIDE;
private:
/// \brief Setups the \c IncludeDirectives for the replacer.
virtual bool handleBeginSource(clang::CompilerInstance &CI,
llvm::StringRef Filename) LLVM_OVERRIDE;
llvm::OwningPtr<IncludeDirectives> IncludeManager;
ConstructorParamReplacer *Replacer;
};
#endif // CPP11_MIGRATE_PASS_BY_VALUE_H

View File

@ -0,0 +1,167 @@
//===-- PassByValueActions.cpp --------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file contains the definition of the ASTMatcher callback for the
/// PassByValue transform.
///
//===----------------------------------------------------------------------===//
#include "PassByValueActions.h"
#include "PassByValueMatchers.h"
#include "Core/IncludeDirectives.h"
#include "Core/Transform.h"
#include "clang/AST/RecursiveASTVisitor.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Lex/Lexer.h"
using namespace clang;
using namespace clang::tooling;
using namespace clang::ast_matchers;
namespace {
/// \brief \c clang::RecursiveASTVisitor that checks that the given
/// \c ParmVarDecl is used exactly one time.
///
/// \see ExactlyOneUsageVisitor::hasExactlyOneUsageIn()
class ExactlyOneUsageVisitor
: public RecursiveASTVisitor<ExactlyOneUsageVisitor> {
friend class RecursiveASTVisitor<ExactlyOneUsageVisitor>;
public:
ExactlyOneUsageVisitor(const ParmVarDecl *ParamDecl) : ParamDecl(ParamDecl) {}
/// \brief Whether or not the parameter variable is referred only once in the
/// given constructor.
bool hasExactlyOneUsageIn(const CXXConstructorDecl *Ctor) {
Count = 0;
TraverseDecl(const_cast<CXXConstructorDecl *>(Ctor));
return Count == 1;
}
private:
/// \brief Counts the number of references to a variable.
///
/// Stops the AST traversal if more than one usage is found.
bool VisitDeclRefExpr(DeclRefExpr *D) {
if (const ParmVarDecl *To = llvm::dyn_cast<ParmVarDecl>(D->getDecl()))
if (To == ParamDecl) {
++Count;
if (Count > 1)
// no need to look further, used more than once
return false;
}
return true;
}
const ParmVarDecl *ParamDecl;
unsigned Count;
};
} // end anonymous namespace
/// \brief Whether or not \p ParamDecl is used exactly one time in \p Ctor.
///
/// Checks both in the init-list and the body of the constructor.
static bool paramReferredExactlyOnce(const CXXConstructorDecl *Ctor,
const ParmVarDecl *ParamDecl) {
ExactlyOneUsageVisitor Visitor(ParamDecl);
return Visitor.hasExactlyOneUsageIn(Ctor);
}
/// \brief Find all references to \p ParamDecl accross all of the
/// redeclarations of \p Ctor.
static void
collectParamDecls(const CXXConstructorDecl *Ctor, const ParmVarDecl *ParamDecl,
llvm::SmallVectorImpl<const ParmVarDecl *> &Results) {
unsigned ParamIdx = ParamDecl->getFunctionScopeIndex();
for (CXXConstructorDecl::redecl_iterator I = Ctor->redecls_begin(),
E = Ctor->redecls_end();
I != E; ++I)
Results.push_back((*I)->getParamDecl(ParamIdx));
}
void ConstructorParamReplacer::run(const MatchFinder::MatchResult &Result) {
assert(IncludeManager && "Include directives manager not set.");
SourceManager &SM = *Result.SourceManager;
const CXXConstructorDecl *Ctor =
Result.Nodes.getNodeAs<CXXConstructorDecl>(PassByValueCtorId);
const ParmVarDecl *ParamDecl =
Result.Nodes.getNodeAs<ParmVarDecl>(PassByValueParamId);
const CXXCtorInitializer *Initializer =
Result.Nodes.getNodeAs<CXXCtorInitializer>(PassByValueInitializerId);
assert(Ctor && ParamDecl && Initializer && "Bad Callback, missing node.");
// Check this now to avoid unecessary work. The param locations are checked
// later.
if (!Owner.isFileModifiable(SM, Initializer->getSourceLocation()))
return;
// The parameter will be in an unspecified state after the move, so check if
// the parameter is used for anything else other than the copy. If so do not
// apply any changes.
if (!paramReferredExactlyOnce(Ctor, ParamDecl))
return;
llvm::SmallVector<const ParmVarDecl *, 2> AllParamDecls;
collectParamDecls(Ctor, ParamDecl, AllParamDecls);
// Generate all replacements for the params.
llvm::SmallVector<Replacement, 2> ParamReplaces(AllParamDecls.size());
for (unsigned I = 0, E = AllParamDecls.size(); I != E; ++I) {
TypeLoc ParamTL = AllParamDecls[I]->getTypeSourceInfo()->getTypeLoc();
SourceRange Range(AllParamDecls[I]->getLocStart(), ParamTL.getLocEnd());
CharSourceRange CharRange = Lexer::makeFileCharRange(
CharSourceRange::getTokenRange(Range), SM, LangOptions());
TypeLoc ValueTypeLoc = ParamTL;
// transform non-value parameters (e.g: const-ref) to values
if (!ParamTL.getNextTypeLoc().isNull())
ValueTypeLoc = ParamTL.getNextTypeLoc();
llvm::SmallString<32> ValueStr = Lexer::getSourceText(
CharSourceRange::getTokenRange(ValueTypeLoc.getSourceRange()), SM,
LangOptions());
// If it's impossible to change one of the parameter (e.g: comes from an
// unmodifiable header) quit the callback now, do not generate any changes.
if (CharRange.isInvalid() || ValueStr.empty() ||
!Owner.isFileModifiable(SM, CharRange.getBegin()))
return;
// 'const Foo &param' -> 'Foo param'
// ~~~~~~~~~~~ ~~~^
ValueStr += ' ';
ParamReplaces[I] = Replacement(SM, CharRange, ValueStr);
}
// Reject the changes if the the risk level is not acceptable.
if (!Owner.isAcceptableRiskLevel(RL_Reasonable)) {
RejectedChanges++;
return;
}
// if needed, include <utility> in the file that uses std::move()
const FileEntry *STDMoveFile =
SM.getFileEntryForID(SM.getFileID(Initializer->getLParenLoc()));
const tooling::Replacement &IncludeReplace =
IncludeManager->addAngledInclude(STDMoveFile, "utility");
if (IncludeReplace.isApplicable()) {
Replaces.insert(IncludeReplace);
AcceptedChanges++;
}
// const-ref params becomes values (const Foo & -> Foo)
Replaces.insert(ParamReplaces.begin(), ParamReplaces.end());
AcceptedChanges += ParamReplaces.size();
// move the value in the init-list
Replaces.insert(Replacement(
SM, Initializer->getLParenLoc().getLocWithOffset(1), 0, "std::move("));
Replaces.insert(Replacement(SM, Initializer->getRParenLoc(), 0, ")"));
AcceptedChanges += 2;
}

View File

@ -0,0 +1,76 @@
//===-- PassByValueActions.h ------------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file contains the declaration of the ASTMatcher callback for the
/// PassByValue transform.
///
//===----------------------------------------------------------------------===//
#ifndef CPP11_MIGRATE_PASS_BY_VALUE_ACTIONS_H
#define CPP11_MIGRATE_PASS_BY_VALUE_ACTIONS_H
#include "clang/ASTMatchers/ASTMatchFinder.h"
#include "clang/Tooling/Refactoring.h"
class Transform;
class IncludeDirectives;
/// \brief Callback that replaces const-ref parameters in constructors to use
/// pass-by-value semantic where applicable.
///
/// Modifications done by the callback:
/// - \#include \<utility\> is added if necessary for the definition of
/// \c std::move() to be available.
/// - The parameter type is changed from const-ref to value-type.
/// - In the init-list the parameter is moved.
///
/// Example:
/// \code
/// + #include <utility>
///
/// class Foo(const std::string &S) {
/// public:
/// - Foo(const std::string &S) : S(S) {}
/// + Foo(std::string S) : S(std::move(S)) {}
///
/// private:
/// std::string S;
/// };
/// \endcode
///
/// \note Since an include may be added by this matcher it's necessary to call
/// \c setIncludeDirectives() with an up-to-date \c IncludeDirectives. This is
/// typically done by overloading \c Transform::handleBeginSource().
class ConstructorParamReplacer
: public clang::ast_matchers::MatchFinder::MatchCallback {
public:
ConstructorParamReplacer(clang::tooling::Replacements &Replaces,
unsigned &AcceptedChanges, unsigned &RejectedChanges,
const Transform &Owner)
: Replaces(Replaces), AcceptedChanges(AcceptedChanges),
RejectedChanges(RejectedChanges), Owner(Owner), IncludeManager(0) {}
void setIncludeDirectives(IncludeDirectives *Includes) {
IncludeManager = Includes;
}
private:
/// \brief Entry point to the callback called when matches are made.
virtual void run(const clang::ast_matchers::MatchFinder::MatchResult &Result)
LLVM_OVERRIDE;
clang::tooling::Replacements &Replaces;
unsigned &AcceptedChanges;
unsigned &RejectedChanges;
const Transform &Owner;
IncludeDirectives *IncludeManager;
};
#endif // CPP11_MIGRATE_PASS_BY_VALUE_ACTIONS_H

View File

@ -0,0 +1,80 @@
//===-- PassByValueMatchers.cpp -------------------------------------------===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file contains the definitions for matcher-generating functions
/// and names for bound nodes found by AST matchers.
///
//===----------------------------------------------------------------------===//
#include "PassByValueMatchers.h"
const char *PassByValueCtorId = "Ctor";
const char *PassByValueParamId = "Param";
const char *PassByValueInitializerId = "Initializer";
namespace clang {
namespace ast_matchers {
/// \brief Matches move constructible classes.
///
/// Given
/// \code
/// // POD types are trivially move constructible
/// struct Foo { int a; };
///
/// struct Bar {
/// Bar(Bar &&) = deleted;
/// int a;
/// };
/// \endcode
/// recordDecl(isMoveConstructible())
/// matches "Foo".
AST_MATCHER(CXXRecordDecl, isMoveConstructible) {
for (CXXRecordDecl::ctor_iterator I = Node.ctor_begin(), E = Node.ctor_end(); I != E; ++I) {
const CXXConstructorDecl *Ctor = *I;
if (Ctor->isMoveConstructor() && !Ctor->isDeleted())
return true;
}
return false;
}
/// \brief Matches non-deleted copy constructors.
///
/// Given
/// \code
/// struct Foo { Foo(const Foo &) = default; };
/// struct Bar { Bar(const Bar &) = deleted; };
/// \endcode
/// constructorDecl(isNonDeletedCopyConstructor())
/// matches "Foo(const Foo &)".
AST_MATCHER(CXXConstructorDecl, isNonDeletedCopyConstructor) {
return Node.isCopyConstructor() && !Node.isDeleted();
}
} // namespace ast_matchers
} // namespace clang
using namespace clang;
using namespace clang::ast_matchers;
DeclarationMatcher makePassByValueCtorParamMatcher() {
return constructorDecl(
forEachConstructorInitializer(ctorInitializer(
// Clang builds a CXXConstructExpr only when it knowns which
// constructor will be called. In dependent contexts a ParenListExpr
// is generated instead of a CXXConstructExpr, filtering out templates
// automatically for us.
withInitializer(constructExpr(
has(declRefExpr(to(parmVarDecl().bind(PassByValueParamId)))),
hasDeclaration(constructorDecl(
isNonDeletedCopyConstructor(),
hasDeclContext(recordDecl(isMoveConstructible())))))))
.bind(PassByValueInitializerId)))
.bind(PassByValueCtorId);
}

View File

@ -0,0 +1,44 @@
//===-- PassByValueMatchers.h -----------------------------------*- C++ -*-===//
//
// The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file contains the declarations for matcher-generating functions
/// and names for bound nodes found by AST matchers.
///
//===----------------------------------------------------------------------===//
#ifndef CPP11_MIGRATE_REPLACE_AUTO_PTR_MATCHERS_H
#define CPP11_MIGRATE_REPLACE_AUTO_PTR_MATCHERS_H
#include "clang/ASTMatchers/ASTMatchers.h"
/// \name Names to bind with matched expressions
/// @{
extern const char *PassByValueCtorId;
extern const char *PassByValueParamId;
extern const char *PassByValueInitializerId;
/// @}
/// \brief Creates a matcher that finds class field initializations that can
/// benefit from using the move constructor.
///
/// \code
/// class A {
/// public:
/// A(const std::string &S) : S(S) {}
/// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ PassByValueCtorId
/// ~~~~~~~~~~~~~~~~~~~~ PassByValueParamId
/// ~ PassByValueInitializerId
/// private:
/// std::string S;
/// };
/// \endcode
clang::ast_matchers::DeclarationMatcher makePassByValueCtorParamMatcher();
#endif // CPP11_MIGRATE_REPLACE_AUTO_PTR_MATCHERS_H

View File

@ -19,6 +19,9 @@ list(APPEND Cpp11MigrateSources ${UseAutoSources})
file(GLOB_RECURSE AddOverrideSources "../AddOverride/*.cpp")
list(APPEND Cpp11MigrateSources ${AddOverrideSources})
file(GLOB_RECURSE PassByValueSources "../PassByValue/*.cpp")
list(APPEND Cpp11MigrateSources ${PassByValueSources})
file(GLOB_RECURSE ReplaceAutoPtrSources "../ReplaceAutoPtr/*.cpp")
list(APPEND Cpp11MigrateSources ${ReplaceAutoPtrSources})

View File

@ -414,6 +414,7 @@ int main(int argc, const char **argv) {
// These anchors are used to force the linker to link the transforms
extern volatile int AddOverrideTransformAnchorSource;
extern volatile int LoopConvertTransformAnchorSource;
extern volatile int PassByValueTransformAnchorSource;
extern volatile int ReplaceAutoPtrTransformAnchorSource;
extern volatile int UseAutoTransformAnchorSource;
extern volatile int UseNullptrTransformAnchorSource;
@ -421,6 +422,7 @@ extern volatile int UseNullptrTransformAnchorSource;
static int TransformsAnchorsDestination[] = {
AddOverrideTransformAnchorSource,
LoopConvertTransformAnchorSource,
PassByValueTransformAnchorSource,
ReplaceAutoPtrTransformAnchorSource,
UseAutoTransformAnchorSource,
UseNullptrTransformAnchorSource

View File

@ -30,6 +30,8 @@ SOURCES += $(addprefix ../UseAuto/,$(notdir $(wildcard $(PROJ_SRC_DIR)/../UseAut
BUILT_SOURCES += $(ObjDir)/../UseAuto/.objdir
SOURCES += $(addprefix ../AddOverride/,$(notdir $(wildcard $(PROJ_SRC_DIR)/../AddOverride/*.cpp)))
BUILT_SOURCES += $(ObjDir)/../AddOverride/.objdir
SOURCES += $(addprefix ../PassByValue/,$(notdir $(wildcard $(PROJ_SRC_DIR)/../PassByValue/*.cpp)))
BUILT_SOURCES += $(ObjDir)/../PassByValue/.objdir
SOURCES += $(addprefix ../ReplaceAutoPtr/,$(notdir $(wildcard $(PROJ_SRC_DIR)/../ReplaceAutoPtr/*.cpp)))
BUILT_SOURCES += $(ObjDir)/../ReplaceAutoPtr/.objdir

View File

@ -141,6 +141,7 @@ General Command Line Options
=============== ===== === ==== ====
AddOverride (1) 3.0 4.7 14 8
LoopConvert 3.0 4.6 13 11
PassByValue 3.0 4.6 13 11
ReplaceAutoPtr 3.0 4.6 13 11
UseAuto 2.9 4.4 12 10
UseNullptr 3.0 4.6 12.1 10
@ -226,6 +227,12 @@ Transform-Specific Command Line Options
projects that use such macros to maintain build compatibility with non-C++11
code.
.. option:: -pass-by-value
Replace const-reference parameters by values in situations where it can be
beneficial.
See :doc:`PassByValueTransform`.
.. option:: -replace-auto_ptr
Replace ``std::auto_ptr`` (deprecated in C++11) by ``std::unique_ptr`` and

View File

@ -0,0 +1,137 @@
.. index:: Pass-By-Value Transform
=======================
Pass-By-Value Transform
=======================
The Pass-By-Value Transform makes use of the pass-by-value idiom when possible.
With move semantics added to the language and the standard library updated with
move constructors added for many types it is now interesting to take an argument
directly by value, instead of by const-reference, and then copy. This
transformation allows the compiler to take care of choosing the best way to
construct the copy.
The transformation is usually beneficial when the calling code passes an
*rvalue* and assumes the move construction is a cheap operation. This short
example illustrates how the construction of the value happens:
.. code-block:: c++
void foo(std::string s);
std::string get_str();
void f(const std::string &str) {
foo(str); // lvalue -> copy construction
foo(get_str()); // prvalue -> move construction
}
.. note::
Currently only constructors are transformed to make use of pass-by-value.
Contributions that handle other situations are welcome!
Pass-by-value in constructors
-----------------------------
Replaces the uses of const-references constructor parameters that are copied
into class fields. The parameter is then moved with `std::move()`.
Since `std::move()` is a library function declared in `<utility>` it may be
necessary to add this include. The transform will add the include directive when
necessary.
Example::
$ cpp11-migrate -pass-by-value ctor.cpp
**ctor.cpp**
.. code-block:: c++
#include <string>
class Foo {
public:
- Foo(const std::string &Copied, const std::string &ReadOnly)
- : Copied(Copied), ReadOnly(ReadOnly)
+ Foo(std::string Copied, const std::string &ReadOnly)
+ : Copied(std::move(Copied)), ReadOnly(ReadOnly)
{}
private:
std::string Copied;
const std::string &ReadOnly;
};
std::string get_cwd();
void f(const std::string &Path) {
// The parameter corresponding to 'get_cwd()' is move-constructed. By
// using pass-by-value in the Foo constructor we managed to avoid a
// copy-construction.
Foo foo(get_cwd(), Path);
}
If the parameter is used more than once no transformation is performed since
moved objects have an undefined state. It means the following code will be left
untouched:
.. code-block:: c++
#include <string>
void pass(const std::string &S);
struct Foo {
Foo(const std::string &S) : Str(S) {
pass(S);
}
std::string Str;
};
Risk
^^^^
This modification is considered **reasonably safe** (see :option:`-risk`
option).
A situation where the generated code can be wrong is when the object referenced
is modified before the assignment in the init-list through a "hidden" reference.
Example:
.. code-block:: c++
std::string s("foo");
struct Base {
Base() {
s = "bar";
}
};
struct Derived : Base {
- Derived(const std::string &S) : Field(S)
+ Derived(std::string S) : Field(std::move(S))
{ }
std::string Field;
};
void f() {
- Derived d(s); // d.Field holds "bar"
+ Derived d(s); // d.Field holds "foo"
}
.. seealso::
For more information about this idiom, read: `Want Speed? Pass by Value`_.
.. _Want Speed? Pass by Value: http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

View File

@ -11,6 +11,7 @@ C++11 Migrator User's Manual
UseNullptrTransform
LoopConvertTransform
AddOverrideTransform
PassByValueTransform
ReplaceAutoPtrTransform
MigratorUsage
@ -116,4 +117,6 @@ independently enabled. The transforms currently implemented are:
* :doc:`AddOverrideTransform`
* :doc:`PassByValueTransform`
* :doc:`ReplaceAutoPtrTransform`

View File

@ -0,0 +1,164 @@
// RUN: grep -Ev "// *[A-Z-]+:" %s > %t.cpp
// RUN: cpp11-migrate -pass-by-value %t.cpp -- -std=c++11 -I %S
// RUN: FileCheck -input-file=%t.cpp %s
//
// RUN: grep -Ev "// *[A-Z-]+:" %s > %t.cpp
// RUN: cpp11-migrate -pass-by-value %t.cpp -- -std=c++11 -I %S
// RUN: FileCheck -check-prefix=SAFE_RISK -input-file=%t.cpp %s
#include "basic.h"
// CHECK: #include <utility>
// Test that when the class declaration can't be modified we won't modify the
// definition either.
UnmodifiableClass::UnmodifiableClass(const Movable &M) : M(M) {}
// CHECK: UnmodifiableClass::UnmodifiableClass(const Movable &M) : M(M) {}
struct A {
A(const Movable &M) : M(M) {}
// CHECK: A(Movable M) : M(std::move(M)) {}
// SAFE_RISK: A(const Movable &M) : M(M) {}
Movable M;
};
// Test that we aren't modifying other things than a parameter
Movable GlobalObj;
struct B {
B(const Movable &M) : M(GlobalObj) {}
// CHECK: B(const Movable &M) : M(GlobalObj) {}
Movable M;
};
// Test that a parameter with more than one reference to it won't be changed.
struct C {
// Tests extra-reference in body
C(const Movable &M) : M(M) { this->i = M.a; }
// CHECK: C(const Movable &M) : M(M) { this->i = M.a; }
// Tests extra-reference in init-list
C(const Movable &M, int) : M(M), i(M.a) {}
// CHECK: C(const Movable &M, int) : M(M), i(M.a) {}
Movable M;
int i;
};
// Test that both declaration and definition are updated
struct D {
D(const Movable &M);
// CHECK: D(Movable M);
Movable M;
};
D::D(const Movable &M) : M(M) {}
// CHECK: D::D(Movable M) : M(std::move(M)) {}
// Test with default parameter
struct E {
E(const Movable &M = Movable()) : M(M) {}
// CHECK: E(Movable M = Movable()) : M(std::move(M)) {}
Movable M;
};
// Test with object that can't be moved
struct F {
F(const NotMovable &NM) : NM(NM) {}
// CHECK: F(const NotMovable &NM) : NM(NM) {}
NotMovable NM;
};
// Test unnamed parameter in declaration
struct G {
G(const Movable &);
// CHECK: G(Movable );
Movable M;
};
G::G(const Movable &M) : M(M) {}
// CHECK: G::G(Movable M) : M(std::move(M)) {}
// Test parameter with and without qualifier
namespace ns_H {
typedef ::Movable HMovable;
}
struct H {
H(const ns_H::HMovable &M);
// CHECK: H(ns_H::HMovable M);
ns_H::HMovable M;
};
using namespace ns_H;
H::H(const HMovable &M) : M(M) {}
// CHECK: H(HMovable M) : M(std::move(M)) {}
// Try messing up with macros
#define MOVABLE_PARAM(Name) const Movable & Name
// CHECK: #define MOVABLE_PARAM(Name) const Movable & Name
struct I {
I(MOVABLE_PARAM(M)) : M(M) {}
// CHECK: I(MOVABLE_PARAM(M)) : M(M) {}
Movable M;
};
#undef MOVABLE_PARAM
// Test that templates aren't modified
template <typename T> struct J {
J(const T &M) : M(M) {}
// CHECK: J(const T &M) : M(M) {}
T M;
};
J<Movable> j1(Movable());
J<NotMovable> j2(NotMovable());
struct K_Movable {
K_Movable() = default;
K_Movable(const K_Movable &) = default;
K_Movable(K_Movable &&o) { dummy = o.dummy; }
int dummy;
};
// Test with movable type with an user defined move constructor.
struct K {
K(const K_Movable &M) : M(M) {}
// CHECK: K(K_Movable M) : M(std::move(M)) {}
K_Movable M;
};
template <typename T> struct L {
L(const Movable &M) : M(M) {}
// CHECK: L(Movable M) : M(std::move(M)) {}
Movable M;
};
L<int> l(Movable());
// Test with a non-instantiated template class
template <typename T> struct N {
N(const Movable &M) : M(M) {}
// CHECK: N(Movable M) : M(std::move(M)) {}
Movable M;
T A;
};
// Test with value parameter
struct O {
O(Movable M) : M(M) {}
// CHECK: O(Movable M) : M(std::move(M)) {}
Movable M;
};
// Test with a const-value parameter
struct P {
P(const Movable M) : M(M) {}
// CHECK: P(Movable M) : M(std::move(M)) {}
Movable M;
};
// Test with multiples parameters where some need to be changed and some don't
// need to.
struct Q {
Q(const Movable &A, const Movable &B, const Movable &C, double D)
: A(A), B(B), C(C), D(D) {}
// CHECK: Q(const Movable &A, Movable B, Movable C, double D)
// CHECK-NEXT: : A(A), B(std::move(B)), C(std::move(C)), D(D) {}
const Movable &A;
Movable B;
Movable C;
double D;
};

View File

@ -0,0 +1,23 @@
#ifndef BASIC_H
#define BASIC_H
// POD types are trivially move constructible
struct Movable {
int a, b, c;
};
struct NotMovable {
NotMovable() = default;
NotMovable(const NotMovable &) = default;
NotMovable(NotMovable &&) = delete;
int a, b, c;
};
// The test runs the migrator without header modifications enabled for this
// header making the constructor parameter M unmodifiable.
struct UnmodifiableClass {
UnmodifiableClass(const Movable &M);
Movable M;
};
#endif // BASIC_H