Исторически сложилось, что разные языки программирования, поддерживающие работу с исключениями, по-разному относятся к спецификации исключений. В Java спецификации обязательны и контролируются статически, в C# и Python их вообще нет, а в C++ этот вопрос является одним из самых «сырых» мест.
В двух словах о том, что такое спецификация исключений, для тех, кто не сталкивался с этим понятием. Спецификация исключений — это явное описание тех типов исключений, которые могут быть сгенерированы некой функцией. В java, языке, который поддерживает наилучшим образом спецификацию исключений, это выглядит так

void funct() throw MyException
{
throw new MyException();
}

MyException — это единственно возможный тип исключений, который может покинуть метод. При спецификации можно указать несколько типов исключений.

C++

С самого начала, идея спецификации исключений была чужда языку C++. Язык унаследовал миллиарды строк кода на языке C и, к тому времени, уже были написаны миллионы строк кода на C++, в которых не было ничего о спецификации исключений. Тем не менее, в C++ была добавлена возможность спецификаций. В своей книге «Дизайн и эволюция C++» Бьёрн Страуструп пишет о том, что спецификации, изначально могли контролироваться только во время исполнения, но, позже, были добавлены некоторые возможности для статического контроля, но, как мы увидим, этого не достаточно. В том же параграфе, Бьёрн приводит пример, который я сейчас Вам продемонстрирую. Итак, предположим, что у нас есть библиотека, написанная на языке C++, вот заголовок этой библиотеки

#ifndef SHAREDCPP_H
#define SHAREDCPP_H

#ifdef __cplusplus
extern "C" {
#endif

void foo();

#ifdef __cplusplus
} // extern "C"
#endif

#endif // SHAREDCPP_H

И её реализация

#include <iostream>
#include <string>

extern "C" {

void foo()
{
std::cout << "Throw exceptionn";
throw std::string("test exception");
}

}

Я назвал эту библиотеку libsharedcpp. Я использую ОС Linux, поэтому я не писал конструкции типа __declspec(dllexport). Если Вы используете ОС MS Windows, то для компиляции примеров, Вам нужно будет добавить эти конструкции (думаю, не нужно Вас учить это делать). Для компиляции, я буду использовать компиляторы из набора GCC. Собираем эту библиотеку командой

g++ -shared -fPIC sharedcpp.cpp -o libsharedcpp.so

Теперь создадим библиотеку libsharedc на языке C со следующим кодом

#ifndef SHAREDC_H
#define SHAREDC_H

#ifdef __cplusplus
extern "C" {
#endif

void bar();

#ifdef __cplusplus
} // extern "C"
#endif

#endif // SHAREDC_H
#include "sharedc.h"
#include "sharedcpp.h"

void bar()
{
foo();
}

Как видно, библиотека libsharedc использует функцию из библиотеки libsharedcpp, поэтому, при компиляции, надо указать этот факт

gcc -shared -fPIC sharedc.c -lsharedcpp -L. -o libsharedc.so

Теперь создадим исполняемый модуль на языке C++.

#include <string>
#include <iostream>
#include "sharedc.h"

int main()
{
try
{
bar();
}
catch(const std::string & str)
{
std::cout << str << std::endl;
}
return 0;
}

Если Вы используете UNIX-like ОС, то, перед компиляцией программы, Вам следует либо поместить собранные библиотеки в место, где Ваша ОС их найдёт, например в /usr/lib, либо добавить в путь для поиска текущий каталог командой

export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:`pwd`

Далее собираем программу командой

g++ program.cpp -lsharedc -L. -o program

Запустим программу

$ ./program 
Throw exception
test exception

Итак, что же я хотел продемонстрировать этой программой. Прежде всего, обратите внимание, на то, что исключение прошло через два скомпилированных модуля. Мало того, один из скомпилированных модулей написан на языке C, который знать не знает ни о каких исключениях. Из этого уже можно сделать вывод о том, что указать спецификацию исключений для функции bar невозможно.
Теперь, давайте дополним программу таким образом

#include <string>
#include <exception>
#include <iostream>
#include "sharedc.h"

void test() throw(std::exception)
{
bar();
}

int main()
{
try
{
test();
}
catch(const std::exception & err)
{
}
return 0;
}

Я добавил функцию test, которая специфицирует исключения только стандартного типа — std::exception. Статические анализаторы не смогут определить, что функция bar генерирует исключение типа std::string, и компиляция пройдёт успешно. Но при выполнении нас ждёт ошибка.

$ ./program 
Throw exception
terminate called after throwing an instance of 'std::string'
Аварийный останов

И даже добавление catch блока, обрабатывающего std::string, не устранит проблему , так как она возникает до момента обработки исключения, а именно — в момент выхода исключения из функции.

try
{
test();
}
catch(const std::exception & err)
{
}
catch(const std::string & err)
{
}

Таким образом, я продемонстрировал ситуацию, когда пользы от спецификации исключений меньше, чем вреда.

Java

По-другому дела обстоят с Java. В этом языке спецификация исключений контролируется статически, а компиляция в байт-код позволяет выявить все спецификации на этапе компиляции или раньше. В отличие от C++, спецификация исключений в Java носит обязательный характер, что тоже сопряжено с некоторыми трудностями.
Предположим, что у нас в программе, есть базовый класс

package test;

public class MyBase
{

private int value;

public MyBase(int value)
{
this.value = value;
}
}

И от него наследуются множество наследников. Например

public class MyFirst
extends MyBase
{

public MyFirst()
{
super(1);
}
}

И, некоторый наследник содержит в себе данные, исходный код которых, не доступен

package test;

import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.SparseMultigraph;

public class MySecons
extends MyFirst
{

private Graph<Integer, String> graph;

public MySecons()
{
graph = new SparseMultigraph<Integer, String>();
}
}

Внезапно Вам становится необходимо сделать Ваш базовый класс клонируемым и Вы добавляете переопределение метода clone и наследование от интерфейса Cloneable

package test;

public class MyBase
implements Cloneable
{

private int value;

public MyBase(int value)
{
this.value = value;
}

@Override
public Object clone()
{
try
{
MyBase base = (MyBase)super.clone();
base.value = value;
return base;
} catch(CloneNotSupportedException ex)
{
// Этого не должно произойти.
return null;
}
}
}

Я задушил исключение, так как оно не может возникнуть, если класс реализует интерфейс Cloneable. Теперь я должен реализовать метод clone в потомках базового класса. С классом MyFirst проблем нет; он достаточно примитивен, чтобы не писать эту реализацию вообще. Но, вот, класс MySecond принесёт нам не мало хлопот. Склонировать граф из библиотеки JUNG мы не можем, так как он не предоставляет нам такой возможности. После некоторых раздумий, мы можем прийти к выводу, что клонировать граф — действительно не лучшая затея, и мы решаем запретить клонировать объекты класса MySecond. Но мы уже разрешили клонировать базовый класс и всех его детей, поэтому единственным верным решением остаётся выкинуть исключение при попытке вызова метода clone. Но не тут-то было. В java запрещается специфицировать исключения у переопределённых методов таким образом, чтобы это расходилось со спецификацией, определённой в родительском классе (убирать исключения из спецификации можно). И теперь, как ни старайся, ничего с методом clone, дельного не выйдет, либо возвращаем задушенное исключение, либо придумываем другой способ копирования. Я, к слову, смог ввести некое подобие конструкторов копирования из C++. Другой вариант решения проблемы описан в статье «Фабрика клонов».

Итого

Я не могу говорить за большинство языков, которые существуют, но о некоторых сказать кое-что могу. В частности, в языках C# и Python от спецификации исключений отказались вовсе, и правильно сделали, на мой взгляд. Как видно из предыдущих частей статьи: спецификации исключений зачастую оказываются вредными, а в случае с C++, ещё и бесполезными.