A global variable is shared across 2 concurrently running threads on 2 different cores. The threads writes to and read from the variables. For the atomic variable can one thread read a stale value? Each core might have a value of the shared variable in its cache and when one threads writes to its copy in a cache the other thread on a different core might read stale value from its own cache. Or the compiler does strong memory ordering to read the latest value from the other cache? The c++11 standard library has std::atomic support. How this is different from the volatile keyword? How volatile and atomic types will behave differently in the above scenario?
C++ – Concurrency: Atomic and volatile in C++11 memory model
cc++11concurrencymultithreadingparallel-processing
Related Solutions
A pointer can be re-assigned:
int x = 5; int y = 6; int *p; p = &x; p = &y; *p = 10; assert(x == 5); assert(y == 10);
A reference cannot be re-bound, and must be bound at initialization:
int x = 5; int y = 6; int &q; // error int &r = x;
A pointer variable has its own identity: a distinct, visible memory address that can be taken with the unary
&
operator and a certain amount of space that can be measured with thesizeof
operator. Using those operators on a reference returns a value corresponding to whatever the reference is bound to; the reference’s own address and size are invisible. Since the reference assumes the identity of the original variable in this way, it is convenient to think of a reference as another name for the same variable.int x = 0; int &r = x; int *p = &x; int *p2 = &r; assert(p == p2); // &x == &r assert(&p != &p2);
You can have arbitrarily nested pointers to pointers offering extra levels of indirection. References only offer one level of indirection.
int x = 0; int y = 0; int *p = &x; int *q = &y; int **pp = &p; **pp = 2; pp = &q; // *pp is now q **pp = 4; assert(y == 4); assert(x == 2);
A pointer can be assigned
nullptr
, whereas a reference must be bound to an existing object. If you try hard enough, you can bind a reference tonullptr
, but this is undefined and will not behave consistently./* the code below is undefined; your compiler may optimise it * differently, emit warnings, or outright refuse to compile it */ int &r = *static_cast<int *>(nullptr); // prints "null" under GCC 10 std::cout << (&r != nullptr ? "not null" : "null") << std::endl; bool f(int &r) { return &r != nullptr; } // prints "not null" under GCC 10 std::cout << (f(*static_cast<int *>(nullptr)) ? "not null" : "null") << std::endl;
You can, however, have a reference to a pointer whose value is
nullptr
.Pointers can iterate over an array; you can use
++
to go to the next item that a pointer is pointing to, and+ 4
to go to the 5th element. This is no matter what size the object is that the pointer points to.A pointer needs to be dereferenced with
*
to access the memory location it points to, whereas a reference can be used directly. A pointer to a class/struct uses->
to access its members whereas a reference uses a.
.References cannot be put into an array, whereas pointers can be (Mentioned by user @litb)
Const references can be bound to temporaries. Pointers cannot (not without some indirection):
const int &x = int(12); // legal C++ int *y = &int(12); // illegal to take the address of a temporary.
This makes
const &
more convenient to use in argument lists and so forth.
UPDATE
This answer is rather old, and so describes what was 'good' at the time, which was smart pointers provided by the Boost library. Since C++11, the standard library has provided sufficient smart pointers types, and so you should favour the use of std::unique_ptr
, std::shared_ptr
and std::weak_ptr
.
There was also std::auto_ptr
. It was very much like a scoped pointer, except that it also had the "special" dangerous ability to be copied — which also unexpectedly transfers ownership.
It was deprecated in C++11 and removed in C++17, so you shouldn't use it.
std::auto_ptr<MyObject> p1 (new MyObject());
std::auto_ptr<MyObject> p2 = p1; // Copy and transfer ownership.
// p1 gets set to empty!
p2->DoSomething(); // Works.
p1->DoSomething(); // Oh oh. Hopefully raises some NULL pointer exception.
OLD ANSWER
A smart pointer is a class that wraps a 'raw' (or 'bare') C++ pointer, to manage the lifetime of the object being pointed to. There is no single smart pointer type, but all of them try to abstract a raw pointer in a practical way.
Smart pointers should be preferred over raw pointers. If you feel you need to use pointers (first consider if you really do), you would normally want to use a smart pointer as this can alleviate many of the problems with raw pointers, mainly forgetting to delete the object and leaking memory.
With raw pointers, the programmer has to explicitly destroy the object when it is no longer useful.
// Need to create the object to achieve some goal
MyObject* ptr = new MyObject();
ptr->DoSomething(); // Use the object in some way
delete ptr; // Destroy the object. Done with it.
// Wait, what if DoSomething() raises an exception...?
A smart pointer by comparison defines a policy as to when the object is destroyed. You still have to create the object, but you no longer have to worry about destroying it.
SomeSmartPtr<MyObject> ptr(new MyObject());
ptr->DoSomething(); // Use the object in some way.
// Destruction of the object happens, depending
// on the policy the smart pointer class uses.
// Destruction would happen even if DoSomething()
// raises an exception
The simplest policy in use involves the scope of the smart pointer wrapper object, such as implemented by boost::scoped_ptr
or std::unique_ptr
.
void f()
{
{
std::unique_ptr<MyObject> ptr(new MyObject());
ptr->DoSomethingUseful();
} // ptr goes out of scope --
// the MyObject is automatically destroyed.
// ptr->Oops(); // Compile error: "ptr" not defined
// since it is no longer in scope.
}
Note that std::unique_ptr
instances cannot be copied. This prevents the pointer from being deleted multiple times (incorrectly). You can, however, pass references to it around to other functions you call.
std::unique_ptr
s are useful when you want to tie the lifetime of the object to a particular block of code, or if you embedded it as member data inside another object, the lifetime of that other object. The object exists until the containing block of code is exited, or until the containing object is itself destroyed.
A more complex smart pointer policy involves reference counting the pointer. This does allow the pointer to be copied. When the last "reference" to the object is destroyed, the object is deleted. This policy is implemented by boost::shared_ptr
and std::shared_ptr
.
void f()
{
typedef std::shared_ptr<MyObject> MyObjectPtr; // nice short alias
MyObjectPtr p1; // Empty
{
MyObjectPtr p2(new MyObject());
// There is now one "reference" to the created object
p1 = p2; // Copy the pointer.
// There are now two references to the object.
} // p2 is destroyed, leaving one reference to the object.
} // p1 is destroyed, leaving a reference count of zero.
// The object is deleted.
Reference counted pointers are very useful when the lifetime of your object is much more complicated, and is not tied directly to a particular section of code or to another object.
There is one drawback to reference counted pointers — the possibility of creating a dangling reference:
// Create the smart pointer on the heap
MyObjectPtr* pp = new MyObjectPtr(new MyObject())
// Hmm, we forgot to destroy the smart pointer,
// because of that, the object is never destroyed!
Another possibility is creating circular references:
struct Owner {
std::shared_ptr<Owner> other;
};
std::shared_ptr<Owner> p1 (new Owner());
std::shared_ptr<Owner> p2 (new Owner());
p1->other = p2; // p1 references p2
p2->other = p1; // p2 references p1
// Oops, the reference count of of p1 and p2 never goes to zero!
// The objects are never destroyed!
To work around this problem, both Boost and C++11 have defined a weak_ptr
to define a weak (uncounted) reference to a shared_ptr
.
Related Topic
- C++ – The Definitive C++ Book Guide and List
- The difference between concurrency and parallelism
- C++ – What are rvalues, lvalues, xvalues, glvalues, and prvalues
- C++11 introduced a standardized memory model. What does it mean? And how is it going to affect C++ programming
- Rounded UIView with Shadow
- C# – System.Windows.Application.GetResourceStream returns null
Best Answer
Firstly,
volatile
does not imply atomic access. It is designed for things like memory mapped I/O and signal handling.volatile
is completely unnecessary when used withstd::atomic
, and unless your platform documents otherwise,volatile
has no bearing on atomic access or memory ordering between threads.If you have a global variable which is shared between threads, such as:
then the visibility and ordering constraints depend on the memory ordering parameter you use for operations, and the synchronization effects of locks, threads and accesses to other atomic variables.
In the absence of any additional synchronization, if one thread writes a value to
ai
then there is nothing that guarantees that another thread will see the value in any given time period. The standard specifies that it should be visible "in a reasonable period of time", but any given access may return a stale value.The default memory ordering of
std::memory_order_seq_cst
provides a single global total order for allstd::memory_order_seq_cst
operations across all variables. This doesn't mean that you can't get stale values, but it does mean that the value you do get determines and is determined by where in this total order your operation lies.If you have 2 shared variables
x
andy
, initially zero, and have one thread write 1 tox
and another write 2 toy
, then a third thread that reads both may see either (0,0), (1,0), (0,2) or (1,2) since there is no ordering constraint between the operations, and thus the operations may appear in any order in the global order.If both writes are from the same thread, which does
x=1
beforey=2
and the reading thread readsy
beforex
then (0,2) is no longer a valid option, since the read ofy==2
implies that the earlier write tox
is visible. The other 3 pairings (0,0), (1,0) and (1,2) are still possible, depending how the 2 reads interleave with the 2 writes.If you use other memory orderings such as
std::memory_order_relaxed
orstd::memory_order_acquire
then the constraints are relaxed even further, and the single global ordering no longer applies. Threads don't even necessarily have to agree on the ordering of two stores to separate variables if there is no additional synchronization.The only way to guarantee you have the "latest" value is to use a read-modify-write operation such as
exchange()
,compare_exchange_strong()
orfetch_add()
. Read-modify-write operations have an additional constraint that they always operate on the "latest" value, so a sequence ofai.fetch_add(1)
operations by a series of threads will return a sequence of values with no duplicates or gaps. In the absence of additional constraints, there's still no guarantee which threads will see which values though. In particular, it is important to note that the use of an RMW operation does not force changes from other threads to become visible any quicker, it just means that if the changes are not seen by the RMW then all threads must agree that they are later in the modification order of that atomic variable than the RMW operation. Stores from different threads can still be delayed by arbitrary amounts of time, depending on when the CPU actually issues the store to memory (rather than just its own store buffer), physically how far apart the CPUs executing the threads are (in the case of a multi-processor system), and the details of the cache coherency protocol.Working with atomic operations is a complex topic. I suggest you read a lot of background material, and examine published code before writing production code with atomics. In most cases it is easier to write code that uses locks, and not noticeably less efficient.