Exception handling in C++ is a well-unschooled topic if you observe initial stages of the learning curve. There are numerous tutorials available online on exception handling in C++ with example. But few explains what you should not do & intricacies around it. So here, we will see some intricacies, from where & why you should not throw an exception along with some newer features introduced in Modern C++ on exception handling with example. I am not an expert but this is what I have gained from various sources, courses & industry experiences.
/!\: Originally published @ www.vishalchovatiya.com.
In the end, we will see the performance cost of using an exception by a quick benchmark code. Finally, we will close the article with Best practices & some CPP Core Guidelines on exception handling.
Note: we will not see anything regarding a dynaic exception as it deprecated from C++11 and removed in C++17.

Terminology/Jargon/Idiom you may face

1. Implement copy and/or move constructor while throwing user-defined type object

struct demo
{
    demo() = default;
    demo(demo &&) = delete;
    demo(const demo &) = delete;
};
int main()
{
    throw demo{};
    return 0;
}
TL;DR
class used for throwing the exception object needs copy and/or move constructors

2. Be cautious while throwing an exception from the constructor

struct base
{
    base(){cout<<"base\n";}
    ~base(){cout<<"~base\n";}
};
struct derive : base
{
    derive(){cout<<"derive\n"; throw -1;}
    ~derive(){cout<<"~derive\n";}
};
int main()
{
    try{
        derive{};
    }
    catch (...){}
    return 0;
}
base
derive
~base
struct base
{
    base() { cout << "base\n"; }
    ~base() { cout << "~base\n"; }
};
struct derive : base
{
    derive() = default;
    derive(int) : derive{}
    {
        cout << "derive\n";
        throw - 1;
    }
    ~derive() { cout << "~derive\n"; }
};
int main()
{
    try{
        derive{0};
    }
    catch (...){}
    return 0;
}
base
derive
~derive
~base
TL;DR
When an exception is thrown from a constructor, destructors for the object will be called only & only if an object is created successfully

3. Avoid throwing exceptions out of a destructor

struct demo
{
    ~demo() { throw std::exception{}; }
};
int main()
{
    try{
        demo d;
    }
    catch (const std::exception &){}
    return 0;
}
$ clang++-7 -o main main.cpp
warning: '~demo' has a non-throwing exception specification but can still
      throw [-Wexceptions]
    ~demo() { throw std::exception{}; }
              ^
note: destructor has a implicit non-throwing exception specification
    ~demo() { throw std::exception{}; }
    ^
1 warning generated.
$
$ ./main
terminate called after throwing an instance of 'std::exception'
  what():  std::exception
exited, aborted
struct X
{
    ~X() noexcept(false) { throw std::exception{}; } 
};
Why you should not throw an exception from a destructor?
Because destructors are called during stack unwinding when an exception is thrown, and we are not allowed to throw another exception while the previous one is not caught – in such a case 
std::terminate
 will be called.
struct base
{
    ~base() noexcept(false) { throw 1; }
};
struct derive : base
{
    ~derive() noexcept(false) { throw 2; }
};
int main()
{
    try{
        derive d;
    }
    catch (...){ }
    return 0;
}
int main()
{
    cout << std::boolalpha << std::is_nothrow_destructible<std::string>::value << endl;
    cout << std::boolalpha << std::is_nothrow_constructible<std::string>::value << endl;
    return 0;
}
TL;DR
1. Destructors are by default 
noexcept
(i.e. non-throwing).
2. You should not throw exception out of destructors because destructors are called during stack unwinding when an exception is thrown, and we are not allowed to throw another exception while the previous one is not caught – in such a case 
std::terminate
 will be called.

4. Rethrowing/Nested exception handling with std::exception_ptr( C++11) example

This is more of a demonstration rather the best practice of the nested exception scenario using 
std::exception_ptr
. Although you can simply use 
std::exception
 without complicating things much but 
std::exception_ptr
 will provide us with the leverage of handling exception out of 
try
 / 
catch
 clause.
void print_nested_exception(const std::exception_ptr &eptr=std::current_exception(), size_t level=0)
{
    static auto get_nested = [](auto &e) -> std::exception_ptr {
        try { return dynamic_cast<const std::nested_exception &>(e).nested_ptr(); }
        catch (const std::bad_cast&) { return nullptr; }
    };
    try{
        if (eptr) std::rethrow_exception(eptr);
    }
    catch (const std::exception &e){
        std::cerr << std::string(level, ' ') << "exception: " << e.what() << '\n';
        print_nested_exception(get_nested(e), level + 1);// rewind all nested exception
    }
}
// -----------------------------------------------------------------------------------------------
void func2(){
    try         { throw std::runtime_error("TESTING NESTED EXCEPTION SUCCESS"); }
    catch (...) { std::throw_with_nested(std::runtime_error("func2() failed")); }
}
void func1(){
    try         { func2(); }
    catch (...) { std::throw_with_nested(std::runtime_error("func1() failed")); }
}
int main()
{
    try                             { func1(); }
    catch (const std::exception&)   { print_nested_exception(); }
    return 0;
}
// Will only work with C++14 or above
exception: func1() failed
 exception: func2() failed
  exception: TESTING NESTED EXCEPTION SUCCESS
std::exception_ptr e1;                                             // null
std::exception_ptr e2 = std::current_exception();                  // null or a current exception
std::exception_ptr e3 = std::make_exception_ptr(std::exception{}); // std::exception
TL;DR
1. 
std::exception_ptr
 extends the lifetime of a pointed exception object beyond a catch clause.
2. We may use 
std::exception_ptr
 to delay the handling of a current exception and transfer it to some other palaces. Though, practical usecase of 
std::exception_ptr
 is between threads.

5. Use noexcept specifier vs operator appropriately

void func() throw(std::exception);                   // dynamic excpetions, removed from C++17
void potentially_throwing();                         // may throw
void non_throwing() noexcept;                        // "specifier" specifying non-throwing function
void print() {}                                  
void (*func_ptr)() noexcept = print;                 // Not OK from C++17, `print()` should be noexcept too, works in C++11/14
void debug_deep() noexcept(false) {}                 // specifier specifying throw
void debug() noexcept(noexcept(debug_deep())) {}     // specifier & operator, will follow exception rule of `debug_deep`
auto l_non_throwing = []() noexcept {};              // Yeah..! lambdas are also in party
noexcept specifier
I think this needs no introduction it does what its name suggests. So let’s quickly go through some pointers:
noexcept operator & what is it used for?
class demo
{
public:
    demo() {}
    demo(const demo &) {}
    demo(demo &&) {}
    void method() {}
};
int main()
{
    cout << std::boolalpha << noexcept(demo()) << endl;                        // C
    cout << std::boolalpha << noexcept(demo(demo())) << endl;                  // CC
    cout << std::boolalpha << noexcept(demo(std::declval<demo>())) << endl;    // MC
    cout << std::boolalpha << noexcept(std::declval<demo>().method()) << endl; // Methods
}
// std::declval<T> returns an rvalue reference to a type
TL;DR
noexcept
 specifier & operator are two different things. 
noexcept
 operator performs a compile-time check & doesn’t evaluate the expression. While 
noexcept
 specifier can take only constant expressions that evaluate to either true or false.

6. Move exception-safe with
std::move_if_noexcept

struct demo
{
    demo() = default;
    demo(const demo &) { cout << "Copying\n"; }
    // Exception safe move constructor
    demo(demo &&) noexcept { cout << "Moving\n"; }
private:
    std::vector<int>    m_v;
};
int main()
{
    demo obj1;
    if (noexcept(demo(std::declval<demo>()))){  // if moving safe
        demo obj2(std::move(obj1));             // then move it
    }
    else{
        demo obj2(obj1);                        // otherwise copy it
    }
    demo obj3(std::move_if_noexcept(obj1));     // Alternatively you can do this----------------
    return 0;
}
TL;DR
Move critical object safely with 
std::move_if_noexcept

7. Real cost of exception handling in C++ with benchmark example

Despite many benefits, most people still do not prefer to use exceptions due to its overhead. So let’s clear it out of the way:
static void without_exception(benchmark::State &state){
    for (auto _ : state){
        std::vector<uint32_t> v(10000);
        for (uint32_t i = 0; i < 10000; i++) v.at(i) = i;        
    }
}
BENCHMARK(without_exception);//----------------------------------------
static void with_exception(benchmark::State &state){
    for (auto _ : state){
        std::vector<uint32_t> v(10000);
        for (uint32_t i = 0; i < 10000; i++){
            try{
                v.at(i) = i;
            }
            catch (const std::out_of_range &oor){}
        }
    }
}
BENCHMARK(with_exception);//--------------------------------------------
static void throwing_exception(benchmark::State &state){
    for (auto _ : state){
        std::vector<uint32_t> v(10000);
        for (uint32_t i = 1; i < 10001; i++){
            try{
                v.at(i) = i;
            }
            catch (const std::out_of_range &oor){}
        }
    }
}
BENCHMARK(throwing_exception);//-----------------------------------------
TL;DR
No instruction related to exception handling is executed until one is thrown so using 
try
 / 
catch
 doesn’t actually decrease performance.

Best practices & some CPP Core Guidelines on exception handling

Best practices for C++ exception handling
Some CPP Core Guidelines